css_parser 1.1.9 → 1.2.1
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/css_parser.rb +6 -7
- data/lib/css_parser/parser.rb +24 -10
- data/lib/css_parser/regexps.rb +52 -6
- data/lib/css_parser/rule_set.rb +178 -92
- data/test/test_css_parser_loading.rb +16 -9
- data/test/test_css_parser_misc.rb +22 -0
- data/test/test_rule_set.rb +4 -4
- data/test/test_rule_set_creating_shorthand.rb +44 -0
- data/test/test_rule_set_expanding_shorthand.rb +45 -0
- metadata +34 -40
data/lib/css_parser.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'addressable/uri'
|
1
2
|
require 'uri'
|
2
3
|
require 'net/https'
|
3
4
|
require 'open-uri'
|
@@ -7,7 +8,7 @@ require 'stringio'
|
|
7
8
|
require 'iconv'
|
8
9
|
|
9
10
|
module CssParser
|
10
|
-
VERSION = '1.
|
11
|
+
VERSION = '1.2.0'
|
11
12
|
|
12
13
|
# Merge multiple CSS RuleSets by cascading according to the CSS 2.1 cascading rules
|
13
14
|
# (http://www.w3.org/TR/REC-CSS2/cascade.html#cascading-order).
|
@@ -139,21 +140,19 @@ module CssParser
|
|
139
140
|
# "http://example.org/style/basic.css").inspect
|
140
141
|
# => "body { background: url('http://example.org/style/yellow.png?abc=123') };"
|
141
142
|
def self.convert_uris(css, base_uri)
|
142
|
-
|
143
|
-
base_uri = URI.parse(base_uri) unless base_uri.kind_of?(URI)
|
143
|
+
base_uri = Addressable::URI.parse(base_uri) unless base_uri.kind_of?(Addressable::URI)
|
144
144
|
|
145
|
-
|
145
|
+
css.gsub(URI_RX) do
|
146
146
|
uri = $1.to_s
|
147
147
|
uri.gsub!(/["']+/, '')
|
148
148
|
# Don't process URLs that are already absolute
|
149
149
|
unless uri =~ /^[a-z]+\:\/\//i
|
150
150
|
begin
|
151
|
-
uri = base_uri
|
151
|
+
uri = base_uri + uri
|
152
152
|
rescue; end
|
153
153
|
end
|
154
|
-
"url('
|
154
|
+
"url('#{uri.to_s}')"
|
155
155
|
end
|
156
|
-
out
|
157
156
|
end
|
158
157
|
end
|
159
158
|
|
data/lib/css_parser/parser.rb
CHANGED
@@ -121,7 +121,7 @@ module CssParser
|
|
121
121
|
import_path = import_rule[0].to_s.gsub(/['"]*/, '').strip
|
122
122
|
|
123
123
|
if options[:base_uri]
|
124
|
-
import_uri = URI.parse(options[:base_uri].to_s).
|
124
|
+
import_uri = Addressable::URI.parse(options[:base_uri].to_s) + Addressable::URI.parse(import_path)
|
125
125
|
load_uri!(import_uri, options[:base_uri], media_types)
|
126
126
|
elsif options[:base_dir]
|
127
127
|
load_file!(import_path, options[:base_dir], media_types)
|
@@ -293,7 +293,7 @@ module CssParser
|
|
293
293
|
#
|
294
294
|
# Deprecated: originally accepted three params: `uri`, `base_uri` and `media_types`
|
295
295
|
def load_uri!(uri, options = {}, deprecated = nil)
|
296
|
-
uri = URI.parse(uri) unless uri.respond_to? :scheme
|
296
|
+
uri = Addressable::URI.parse(uri) unless uri.respond_to? :scheme
|
297
297
|
#base_uri = nil, media_types = :all, options = {}
|
298
298
|
|
299
299
|
opts = {:base_uri => nil, :media_types => :all}
|
@@ -324,6 +324,7 @@ module CssParser
|
|
324
324
|
def load_file!(file_name, base_dir = nil, media_types = :all)
|
325
325
|
file_name = File.expand_path(file_name, base_dir)
|
326
326
|
return unless File.readable?(file_name)
|
327
|
+
return unless circular_reference_check(file_name)
|
327
328
|
|
328
329
|
src = IO.read(file_name)
|
329
330
|
base_dir = File.dirname(file_name)
|
@@ -334,6 +335,21 @@ module CssParser
|
|
334
335
|
|
335
336
|
|
336
337
|
protected
|
338
|
+
# Check that a path hasn't been loaded already
|
339
|
+
#
|
340
|
+
# Raises a CircularReferenceError exception if io_exceptions are on,
|
341
|
+
# otherwise returns true/false.
|
342
|
+
def circular_reference_check(path)
|
343
|
+
path = path.to_s
|
344
|
+
if @loaded_uris.include?(path)
|
345
|
+
raise CircularReferenceError, "can't load #{path} more than once" if @options[:io_exceptions]
|
346
|
+
return false
|
347
|
+
else
|
348
|
+
@loaded_uris << path
|
349
|
+
return true
|
350
|
+
end
|
351
|
+
end
|
352
|
+
|
337
353
|
# Strip comments and clean up blank lines from a block of CSS.
|
338
354
|
#
|
339
355
|
# Returns a string.
|
@@ -358,18 +374,12 @@ module CssParser
|
|
358
374
|
# TODO: add option to fail silently or throw and exception on a 404
|
359
375
|
#++
|
360
376
|
def read_remote_file(uri) # :nodoc:
|
361
|
-
|
362
|
-
raise CircularReferenceError, "can't load #{uri.to_s} more than once" if @options[:io_exceptions]
|
363
|
-
return '', nil
|
364
|
-
end
|
365
|
-
|
366
|
-
@loaded_uris << uri.to_s
|
377
|
+
return nil, nil unless circular_reference_check(uri.to_s)
|
367
378
|
|
368
379
|
src = '', charset = nil
|
369
380
|
|
370
381
|
begin
|
371
|
-
uri = URI.parse(uri.to_s)
|
372
|
-
http = Net::HTTP.new(uri.host, uri.port)
|
382
|
+
uri = Addressable::URI.parse(uri.to_s)
|
373
383
|
|
374
384
|
if uri.scheme == 'file'
|
375
385
|
# local file
|
@@ -379,8 +389,12 @@ module CssParser
|
|
379
389
|
else
|
380
390
|
# remote file
|
381
391
|
if uri.scheme == 'https'
|
392
|
+
uri.port = 443 unless uri.port
|
393
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
382
394
|
http.use_ssl = true
|
383
395
|
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
396
|
+
else
|
397
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
384
398
|
end
|
385
399
|
|
386
400
|
res, src = http.get(uri.path, {'User-Agent' => USER_AGENT, 'Accept-Encoding' => 'gzip'})
|
data/lib/css_parser/regexps.rb
CHANGED
@@ -1,4 +1,10 @@
|
|
1
1
|
module CssParser
|
2
|
+
|
3
|
+
|
4
|
+
def self.regex_possible_values *values
|
5
|
+
Regexp.new("([\s]*^)?(#{values.join('|')})([\s]*$)?", 'i')
|
6
|
+
end
|
7
|
+
|
2
8
|
# :stopdoc:
|
3
9
|
# Base types
|
4
10
|
RE_NL = Regexp.new('(\n|\r\n|\r|\f)')
|
@@ -12,6 +18,8 @@ module CssParser
|
|
12
18
|
RE_STRING2 = Regexp.new('(\'(.[^\n\r\f\\\']*|\\\\' + RE_NL.to_s + '|' + RE_ESCAPE.to_s + ')*\')')
|
13
19
|
RE_STRING = Regexp.union(RE_STRING1, RE_STRING2)
|
14
20
|
|
21
|
+
RE_INHERIT = regex_possible_values 'inherit'
|
22
|
+
|
15
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')
|
16
24
|
URI_RX = /url\(("([^"]*)"|'([^']*)'|([^)]*))\)/im
|
17
25
|
|
@@ -23,24 +31,62 @@ module CssParser
|
|
23
31
|
|
24
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
|
25
33
|
#++
|
26
|
-
IMPORTANT_IN_PROPERTY_RX = /[\s]
|
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
|
+
|
27
44
|
STRIP_CSS_COMMENTS_RX = /\/\*.*?\*\//m
|
28
45
|
STRIP_HTML_COMMENTS_RX = /\<\!\-\-|\-\-\>/m
|
29
46
|
|
30
47
|
# Special units
|
31
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
|
32
49
|
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)
|
50
|
+
RE_BACKGROUND_POSITION = Regexp.new("((((#{RE_LENGTH_OR_PERCENTAGE})|left|center|right|top|bottom)[\s]*){1,2})", Regexp::IGNORECASE | Regexp::EXTENDED)
|
34
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
|
+
|
35
55
|
|
36
56
|
# Patterns for specificity calculations
|
37
|
-
|
38
|
-
|
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
|
39
84
|
|
40
85
|
# Colours
|
41
|
-
|
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)
|
42
88
|
RE_COLOUR_HEX = /(#([0-9a-f]{6}|[0-9a-f]{3})([\s;]|$))/i
|
43
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
|
44
|
-
RE_COLOUR = Regexp.union(
|
90
|
+
RE_COLOUR = Regexp.union(RE_COLOUR_NUMERIC, RE_COLOUR_NUMERIC_ALPHA, RE_COLOUR_HEX, RE_COLOUR_NAMED)
|
45
91
|
# :startdoc:
|
46
92
|
end
|
data/lib/css_parser/rule_set.rb
CHANGED
@@ -4,6 +4,9 @@ module CssParser
|
|
4
4
|
RE_ELEMENTS_AND_PSEUDO_ELEMENTS = /((^|[\s\+\>]+)[\w]+|\:(first\-line|first\-letter|before|after))/i
|
5
5
|
RE_NON_ID_ATTRIBUTES_AND_PSEUDO_CLASSES = /(\.[\w]+)|(\[[\w]+)|(\:(link|first\-child|lang))/i
|
6
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
|
+
|
7
10
|
# Array of selector strings.
|
8
11
|
attr_reader :selectors
|
9
12
|
|
@@ -109,65 +112,111 @@ module CssParser
|
|
109
112
|
importance = (options[:force_important] || is_important) ? ' !important' : ''
|
110
113
|
str += "#{prop}: #{val}#{importance}; "
|
111
114
|
end
|
112
|
-
str.gsub(/^[\s]+|[\n\r\f\t]*|[\s]+$/mx, '').strip
|
115
|
+
str.gsub(/^[\s^(\{)]+|[\n\r\f\t]*|[\s]+$/mx, '').strip
|
113
116
|
end
|
114
117
|
|
115
118
|
# Return the CSS rule set as a string.
|
116
119
|
def to_s
|
117
120
|
decs = declarations_to_s
|
118
|
-
"#{@selectors} { #{decs} }"
|
121
|
+
"#{@selectors.join} { #{decs} }"
|
119
122
|
end
|
120
123
|
|
121
124
|
# Split shorthand declarations (e.g. +margin+ or +font+) into their constituent parts.
|
122
125
|
def expand_shorthand!
|
126
|
+
# border must be expanded before dimensions
|
127
|
+
expand_border_shorthand!
|
123
128
|
expand_dimensions_shorthand!
|
124
129
|
expand_font_shorthand!
|
125
130
|
expand_background_shorthand!
|
131
|
+
expand_list_style_shorthand!
|
126
132
|
end
|
127
133
|
|
128
|
-
#
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
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
|
133
172
|
end
|
134
173
|
|
135
174
|
# Split shorthand dimensional declarations (e.g. <tt>margin: 0px auto;</tt>)
|
136
|
-
# into their constituent parts.
|
175
|
+
# into their constituent parts. Handles margin, padding, border-color, border-style and border-width.
|
137
176
|
def expand_dimensions_shorthand! # :nodoc:
|
138
|
-
|
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|
|
139
182
|
|
140
183
|
next unless @declarations.has_key?(property)
|
141
184
|
|
142
185
|
value = @declarations[property][:value]
|
143
|
-
is_important = @declarations[property][:is_important]
|
144
|
-
order = @declarations[property][:order]
|
145
|
-
t, r, b, l = nil
|
146
186
|
|
147
|
-
|
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
|
148
197
|
|
149
198
|
case matches.length
|
150
199
|
when 1
|
151
|
-
t, r, b, l = matches[0]
|
200
|
+
t, r, b, l = matches[0], matches[0], matches[0], matches[0]
|
152
201
|
when 2
|
153
|
-
t, b = matches[0]
|
154
|
-
r, l = matches[1]
|
202
|
+
t, b = matches[0], matches[0]
|
203
|
+
r, l = matches[1], matches[1]
|
155
204
|
when 3
|
156
|
-
t = matches[0]
|
157
|
-
r, l = matches[1]
|
158
|
-
b = matches[2]
|
205
|
+
t = matches[0]
|
206
|
+
r, l = matches[1], matches[1]
|
207
|
+
b = matches[2]
|
159
208
|
when 4
|
160
|
-
t = matches[0]
|
161
|
-
r = matches[1]
|
162
|
-
b = matches[2]
|
163
|
-
l = matches[3]
|
209
|
+
t = matches[0]
|
210
|
+
r = matches[1]
|
211
|
+
b = matches[2]
|
212
|
+
l = matches[3]
|
164
213
|
end
|
165
214
|
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
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
|
+
|
171
220
|
@declarations.delete(property)
|
172
221
|
end
|
173
222
|
end
|
@@ -227,92 +276,105 @@ module CssParser
|
|
227
276
|
@declarations.delete('font')
|
228
277
|
end
|
229
278
|
|
230
|
-
|
231
|
-
# Convert shorthand background declarations (e.g. <tt>background: url("chess.png") gray 50% repeat fixed;</tt>)
|
279
|
+
# Convert shorthand list-style declarations (e.g. <tt>list-style: lower-alpha outside;</tt>)
|
232
280
|
# into their constituent parts.
|
233
281
|
#
|
234
|
-
# See http://www.w3.org/TR/CSS21/
|
235
|
-
def
|
236
|
-
return unless @declarations.has_key?('
|
237
|
-
|
238
|
-
value = @declarations['background'][:value]
|
239
|
-
is_important = @declarations['background'][:is_important]
|
240
|
-
order = @declarations['background'][:order]
|
241
|
-
|
242
|
-
bg_props = {}
|
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')
|
243
285
|
|
286
|
+
value = @declarations['list-style'][:value]
|
244
287
|
|
245
|
-
if
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
if m = value.match(/([\s]*^)?(scroll|fixed)([\s]*$)?/i).to_s
|
251
|
-
bg_props['background-attachment'] = m.strip unless m.empty?
|
288
|
+
if value =~ CssParser::RE_INHERIT
|
289
|
+
LIST_STYLE_PROPERTIES.each do |prop|
|
290
|
+
split_declaration('list-style', prop, 'inherit')
|
291
|
+
end
|
252
292
|
end
|
253
293
|
|
254
|
-
|
255
|
-
|
256
|
-
|
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)))
|
257
297
|
|
258
|
-
|
259
|
-
|
260
|
-
end
|
298
|
+
@declarations.delete('list-style')
|
299
|
+
end
|
261
300
|
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
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
|
269
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
|
270
320
|
|
271
|
-
|
272
|
-
[
|
273
|
-
bg_props["#{prop}"] = 'inherit' unless bg_props.has_key?(prop) and not bg_props[prop].empty?
|
274
|
-
end
|
321
|
+
unless values.empty?
|
322
|
+
@declarations[shorthand_property] = {:value => values.join(' ')}
|
275
323
|
end
|
276
|
-
|
277
|
-
bg_props.each { |bg_prop, bg_val| @declarations[bg_prop] = {:value => bg_val, :is_important => is_important, :order => order} }
|
278
|
-
|
279
|
-
@declarations.delete('background')
|
280
324
|
end
|
281
325
|
|
282
|
-
|
283
|
-
# Looks for long format CSS background properties (e.g. <tt>background-color</tt>) and
|
326
|
+
# Looks for long format CSS background properties (e.g. <tt>background-color</tt>) and
|
284
327
|
# converts them into a shorthand CSS <tt>background</tt> property.
|
285
328
|
#
|
286
329
|
# Leaves properties declared !important alone.
|
287
330
|
def create_background_shorthand! # :nodoc:
|
288
|
-
|
289
|
-
|
290
|
-
|
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|
|
291
342
|
if @declarations.has_key?(property) and not @declarations[property][:is_important]
|
292
|
-
|
293
|
-
|
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]
|
294
347
|
end
|
295
348
|
end
|
296
349
|
|
297
|
-
|
298
|
-
|
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(' ')}
|
299
356
|
end
|
300
357
|
end
|
301
|
-
|
302
|
-
# Looks for long format CSS dimensional properties (
|
303
|
-
# converts them into shorthand CSS properties.
|
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.
|
304
361
|
def create_dimensions_shorthand! # :nodoc:
|
305
|
-
# geometric
|
306
362
|
directions = ['top', 'right', 'bottom', 'left']
|
307
|
-
['margin', 'padding'].each do |property|
|
308
|
-
values = {}
|
309
363
|
|
310
|
-
|
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
|
311
373
|
# All four dimensions must be present
|
312
374
|
if foldable.length == 4
|
313
375
|
values = {}
|
314
376
|
|
315
|
-
directions.each { |d| values[d.to_sym] = @declarations[
|
377
|
+
directions.each { |d| values[d.to_sym] = @declarations[expanded % d][:value].downcase.strip }
|
316
378
|
|
317
379
|
if values[:left] == values[:right]
|
318
380
|
if values[:top] == values[:bottom]
|
@@ -326,17 +388,15 @@ module CssParser
|
|
326
388
|
end
|
327
389
|
else # No sides are equal
|
328
390
|
new_value = values[:top] + ' ' + values[:right] + ' ' + values[:bottom] + ' ' + values[:left]
|
329
|
-
end # done creating 'new_value'
|
330
|
-
|
331
|
-
# Save the new value
|
332
|
-
unless new_value.strip.empty?
|
333
|
-
@declarations[property] = {:value => new_value.gsub(/[\s]+/, ' ').strip}
|
334
391
|
end
|
335
392
|
|
336
|
-
|
337
|
-
|
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) }
|
338
398
|
end
|
339
|
-
end
|
399
|
+
end
|
340
400
|
end
|
341
401
|
|
342
402
|
|
@@ -373,7 +433,33 @@ module CssParser
|
|
373
433
|
|
374
434
|
end
|
375
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
|
+
|
376
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
|
+
|
377
463
|
def parse_declarations!(block) # :nodoc:
|
378
464
|
@declarations = {}
|
379
465
|
|
@@ -52,7 +52,7 @@ class CssParserLoadingTests < Test::Unit::TestCase
|
|
52
52
|
# http://github.com/alexdunae/css_parser/issues#issue/4
|
53
53
|
def test_loading_a_remote_file_over_ssl
|
54
54
|
# TODO: test SSL locally
|
55
|
-
@cp.load_uri!("https://
|
55
|
+
@cp.load_uri!("https://dialect.ca/inc/screen.css")
|
56
56
|
assert_match /margin\: 0\;/, @cp.find_by_selector('body').join(' ')
|
57
57
|
end
|
58
58
|
|
@@ -83,6 +83,14 @@ class CssParserLoadingTests < Test::Unit::TestCase
|
|
83
83
|
# from '/subdir/../simple.css'
|
84
84
|
assert_equal 'margin: 0px;', @cp.find_by_selector('p').join(' ')
|
85
85
|
end
|
86
|
+
|
87
|
+
def test_following_badly_escaped_import_rules
|
88
|
+
css_block = '@import "http://example.com/css?family=Droid+Sans:regular,bold|Droid+Serif:regular,italic,bold,bolditalic&subset=latin";'
|
89
|
+
|
90
|
+
assert_nothing_raised do
|
91
|
+
@cp.add_block!(css_block, :base_uri => "#{@uri_base}/subdir/")
|
92
|
+
end
|
93
|
+
end
|
86
94
|
|
87
95
|
def test_following_at_import_rules_from_add_block
|
88
96
|
css_block = '@import "../simple.css";'
|
@@ -101,20 +109,19 @@ class CssParserLoadingTests < Test::Unit::TestCase
|
|
101
109
|
assert_equal '', @cp.find_by_selector('p', :tty).join(' ')
|
102
110
|
end
|
103
111
|
|
104
|
-
def
|
112
|
+
def test_local_circular_reference_exception
|
105
113
|
assert_raise CircularReferenceError do
|
106
|
-
@cp.
|
114
|
+
@cp.load_file!(File.dirname(__FILE__) + '/fixtures/import-circular-reference.css')
|
107
115
|
end
|
116
|
+
end
|
108
117
|
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
cp_without_exceptions.load_uri!("#{@uri_base}/import-circular-reference.css")
|
118
|
+
def test_remote_circular_reference_exception
|
119
|
+
assert_raise CircularReferenceError do
|
120
|
+
@cp.load_uri!("#{@uri_base}/import-circular-reference.css")
|
113
121
|
end
|
114
|
-
|
115
122
|
end
|
116
123
|
|
117
|
-
def
|
124
|
+
def test_suppressing_circular_reference_exceptions
|
118
125
|
cp_without_exceptions = Parser.new(:io_exceptions => false)
|
119
126
|
|
120
127
|
assert_nothing_raised CircularReferenceError do
|
@@ -139,4 +139,26 @@ class CssParserTests < Test::Unit::TestCase
|
|
139
139
|
assert_equal ".specs {font-family:Helvetica;font-weight:bold;font-style:italic;color:#008CA8;font-size:1.4em;list-style-image:url('http://www.example.org/directory/images/bullet.gif');}", converted_css
|
140
140
|
end
|
141
141
|
|
142
|
+
def test_ruleset_with_braces
|
143
|
+
=begin
|
144
|
+
parser = Parser.new
|
145
|
+
parser.add_block!("div { background-color: black !important; }")
|
146
|
+
parser.add_block!("div { background-color: red; }")
|
147
|
+
|
148
|
+
rulesets = []
|
149
|
+
|
150
|
+
parser['div'].each do |declaration|
|
151
|
+
rulesets << RuleSet.new('div', declaration)
|
152
|
+
end
|
153
|
+
|
154
|
+
merged = CssParser.merge(rulesets)
|
155
|
+
|
156
|
+
result: # merged.to_s => "{ background-color: black !important; }"
|
157
|
+
=end
|
158
|
+
|
159
|
+
new_rule = RuleSet.new('div', "{ background-color: black !important; }")
|
160
|
+
|
161
|
+
assert_equal 'div { background-color: black !important; }', new_rule.to_s
|
162
|
+
end
|
163
|
+
|
142
164
|
end
|
data/test/test_rule_set.rb
CHANGED
@@ -34,13 +34,13 @@ class RuleSetTests < Test::Unit::TestCase
|
|
34
34
|
{:selector => "#content p", :declarations => "color: #fff;", :specificity => 101},
|
35
35
|
{:selector => "a", :declarations => "color: #fff;", :specificity => 1}
|
36
36
|
]
|
37
|
-
|
37
|
+
|
38
38
|
actual = []
|
39
39
|
rs = RuleSet.new('#content p, a', 'color: #fff;')
|
40
40
|
rs.each_selector do |sel, decs, spec|
|
41
41
|
actual << {:selector => sel, :declarations => decs, :specificity => spec}
|
42
42
|
end
|
43
|
-
|
43
|
+
|
44
44
|
assert_equal(expected, actual)
|
45
45
|
end
|
46
46
|
|
@@ -50,13 +50,13 @@ class RuleSetTests < Test::Unit::TestCase
|
|
50
50
|
{:property => 'background', :value => 'white none no-repeat', :is_important => true},
|
51
51
|
{:property => 'color', :value => '#fff', :is_important => false}
|
52
52
|
])
|
53
|
-
|
53
|
+
|
54
54
|
actual = Set.new
|
55
55
|
rs = RuleSet.new(nil, 'color: #fff; Background: white none no-repeat !important; margin: 1px -0.25em;')
|
56
56
|
rs.each_declaration do |prop, val, imp|
|
57
57
|
actual << {:property => prop, :value => val, :is_important => imp}
|
58
58
|
end
|
59
|
-
|
59
|
+
|
60
60
|
assert_equal(expected, actual)
|
61
61
|
end
|
62
62
|
|
@@ -8,6 +8,35 @@ class RuleSetCreatingShorthandTests < Test::Unit::TestCase
|
|
8
8
|
@cp = CssParser::Parser.new
|
9
9
|
end
|
10
10
|
|
11
|
+
# ==== Border shorthand
|
12
|
+
def test_combining_borders_into_shorthand
|
13
|
+
properties = {'border-top-width' => 'auto', 'border-right-width' => 'thin', 'border-bottom-width' => 'auto', 'border-left-width' => '0px'}
|
14
|
+
|
15
|
+
combined = create_shorthand(properties)
|
16
|
+
|
17
|
+
assert_equal('', combined['border'])
|
18
|
+
assert_equal('auto thin auto 0px;', combined['border-width'])
|
19
|
+
|
20
|
+
# after creating shorthand, all long-hand properties should be deleted
|
21
|
+
assert_properties_are_deleted(combined, properties)
|
22
|
+
|
23
|
+
# should not combine if any properties are missing
|
24
|
+
properties.delete('border-top-width')
|
25
|
+
|
26
|
+
combined = create_shorthand(properties)
|
27
|
+
|
28
|
+
assert_equal '', combined['border-width']
|
29
|
+
|
30
|
+
properties = {'border-width' => '22%', 'border-color' => 'rgba(255, 0, 0)'}
|
31
|
+
combined = create_shorthand(properties)
|
32
|
+
assert_equal '22% rgba(255, 0, 0);', combined['border']
|
33
|
+
assert_equal '', combined['border-width']
|
34
|
+
|
35
|
+
properties = {'border-top-style' => 'none', 'border-right-style' => 'none', 'border-bottom-style' => 'none', 'border-left-style' => 'none'}
|
36
|
+
combined = create_shorthand(properties)
|
37
|
+
assert_equal 'none;', combined['border']
|
38
|
+
end
|
39
|
+
|
11
40
|
# ==== Dimensions shorthand
|
12
41
|
def test_combining_dimensions_into_shorthand
|
13
42
|
properties = {'margin-right' => 'auto', 'margin-bottom' => '0px', 'margin-left' => 'auto', 'margin-top' => '0px',
|
@@ -64,6 +93,21 @@ class RuleSetCreatingShorthandTests < Test::Unit::TestCase
|
|
64
93
|
assert_properties_are_deleted(combined, properties)
|
65
94
|
end
|
66
95
|
|
96
|
+
|
97
|
+
# ==== List-style shorthand
|
98
|
+
def test_combining_list_style_into_shorthand
|
99
|
+
properties = {'list-style-image' => 'url(\'chess.png\')', 'list-style-type' => 'katakana',
|
100
|
+
'list-style-position' => 'inside'}
|
101
|
+
|
102
|
+
combined = create_shorthand(properties)
|
103
|
+
|
104
|
+
assert_equal('katakana inside url(\'chess.png\');', combined['list-style'])
|
105
|
+
|
106
|
+
# after creating shorthand, all long-hand properties should be deleted
|
107
|
+
assert_properties_are_deleted(combined, properties)
|
108
|
+
end
|
109
|
+
|
110
|
+
|
67
111
|
def test_property_values_in_url
|
68
112
|
rs = RuleSet.new('#header', "background:url(http://example.com/1528/www/top-logo.jpg) no-repeat top right; padding: 79px 0 10px 0; text-align:left;")
|
69
113
|
rs.expand_shorthand!
|
@@ -7,6 +7,26 @@ class RuleSetExpandingShorthandTests < Test::Unit::TestCase
|
|
7
7
|
@cp = CssParser::Parser.new
|
8
8
|
end
|
9
9
|
|
10
|
+
# ==== Dimensions shorthand
|
11
|
+
def test_expanding_border_shorthand
|
12
|
+
declarations = expand_declarations('border: none')
|
13
|
+
assert_equal 'none', declarations['border-right-style']
|
14
|
+
|
15
|
+
declarations = expand_declarations('border: 1px solid red')
|
16
|
+
assert_equal '1px', declarations['border-top-width']
|
17
|
+
assert_equal 'solid', declarations['border-bottom-style']
|
18
|
+
|
19
|
+
declarations = expand_declarations('border-color: red hsla(255, 0, 0, 5) rgb(2% ,2%,2%)')
|
20
|
+
assert_equal 'red', declarations['border-top-color']
|
21
|
+
assert_equal 'rgb(2%,2%,2%)', declarations['border-bottom-color']
|
22
|
+
assert_equal 'hsla(255,0,0,5)', declarations['border-left-color']
|
23
|
+
|
24
|
+
declarations = expand_declarations('border: thin dot-dot-dash')
|
25
|
+
assert_equal 'dot-dot-dash', declarations['border-left-style']
|
26
|
+
assert_equal 'thin', declarations['border-left-width']
|
27
|
+
assert_nil declarations['border-left-color']
|
28
|
+
end
|
29
|
+
|
10
30
|
# ==== Dimensions shorthand
|
11
31
|
def test_getting_dimensions_from_shorthand
|
12
32
|
# test various shorthand forms
|
@@ -163,6 +183,31 @@ class RuleSetExpandingShorthandTests < Test::Unit::TestCase
|
|
163
183
|
end
|
164
184
|
end
|
165
185
|
|
186
|
+
# ==== List-style shorthand
|
187
|
+
def test_getting_list_style_properties_from_shorthand
|
188
|
+
expected = {'list-style-image' => 'url(\'chess.png\')', 'list-style-type' => 'katakana',
|
189
|
+
'list-style-position' => 'inside'}
|
190
|
+
|
191
|
+
shorthand = "list-style: katakana inside url(\'chess.png\');"
|
192
|
+
declarations = expand_declarations(shorthand)
|
193
|
+
assert_equal expected, declarations
|
194
|
+
end
|
195
|
+
|
196
|
+
def test_getting_list_style_position_from_shorthand
|
197
|
+
['inside', 'outside'].each do |position|
|
198
|
+
shorthand = "list-style: katakana #{position} url('chess.png');"
|
199
|
+
declarations = expand_declarations(shorthand)
|
200
|
+
assert_equal(position, declarations['list-style-position'])
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
def test_getting_list_style_type_from_shorthand
|
205
|
+
['disc', 'circle', 'square', 'decimal', 'decimal-leading-zero', 'lower-roman', 'upper-roman', 'lower-greek', 'lower-alpha', 'lower-latin', 'upper-alpha', 'upper-latin', 'hebrew', 'armenian', 'georgian', 'cjk-ideographic', 'hiragana', 'katakana', 'hira-gana-iroha', 'katakana-iroha', 'none'].each do |type|
|
206
|
+
shorthand = "list-style: #{type} inside url('chess.png');"
|
207
|
+
declarations = expand_declarations(shorthand)
|
208
|
+
assert_equal(type, declarations['list-style-type'])
|
209
|
+
end
|
210
|
+
end
|
166
211
|
|
167
212
|
protected
|
168
213
|
def expand_declarations(declarations)
|
metadata
CHANGED
@@ -1,32 +1,33 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
name: css_parser
|
3
|
-
version: !ruby/object:Gem::Version
|
4
|
-
|
5
|
-
|
6
|
-
- 1
|
7
|
-
- 1
|
8
|
-
- 9
|
9
|
-
version: 1.1.9
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.2.1
|
5
|
+
prerelease:
|
10
6
|
platform: ruby
|
11
|
-
authors:
|
7
|
+
authors:
|
12
8
|
- Alex Dunae
|
13
9
|
autorequire:
|
14
10
|
bindir: bin
|
15
11
|
cert_chain: []
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
12
|
+
date: 2011-09-07 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: addressable
|
16
|
+
requirement: &2154706420 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *2154706420
|
21
25
|
description: A set of classes for parsing CSS in Ruby.
|
22
26
|
email: code@dunae.ca
|
23
27
|
executables: []
|
24
|
-
|
25
28
|
extensions: []
|
26
|
-
|
27
29
|
extra_rdoc_files: []
|
28
|
-
|
29
|
-
files:
|
30
|
+
files:
|
30
31
|
- lib/css_parser.rb
|
31
32
|
- lib/css_parser/parser.rb
|
32
33
|
- lib/css_parser/regexps.rb
|
@@ -46,43 +47,36 @@ files:
|
|
46
47
|
- test/test_rule_set.rb
|
47
48
|
- test/test_rule_set_creating_shorthand.rb
|
48
49
|
- test/test_rule_set_expanding_shorthand.rb
|
49
|
-
|
50
|
-
homepage: http://github.com/alexdunae/css_parser
|
50
|
+
homepage: https://github.com/alexdunae/css_parser
|
51
51
|
licenses: []
|
52
|
-
|
53
52
|
post_install_message:
|
54
|
-
rdoc_options:
|
53
|
+
rdoc_options:
|
55
54
|
- --all
|
56
55
|
- --inline-source
|
57
56
|
- --line-numbers
|
58
57
|
- --charset
|
59
58
|
- utf-8
|
60
|
-
require_paths:
|
59
|
+
require_paths:
|
61
60
|
- lib
|
62
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
61
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
63
62
|
none: false
|
64
|
-
requirements:
|
65
|
-
- -
|
66
|
-
- !ruby/object:Gem::Version
|
67
|
-
|
68
|
-
|
69
|
-
version: "0"
|
70
|
-
required_rubygems_version: !ruby/object:Gem::Requirement
|
63
|
+
requirements:
|
64
|
+
- - ! '>='
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: '0'
|
67
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
71
68
|
none: false
|
72
|
-
requirements:
|
73
|
-
- -
|
74
|
-
- !ruby/object:Gem::Version
|
75
|
-
|
76
|
-
- 0
|
77
|
-
version: "0"
|
69
|
+
requirements:
|
70
|
+
- - ! '>='
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
version: '0'
|
78
73
|
requirements: []
|
79
|
-
|
80
74
|
rubyforge_project:
|
81
|
-
rubygems_version: 1.
|
75
|
+
rubygems_version: 1.8.5
|
82
76
|
signing_key:
|
83
77
|
specification_version: 3
|
84
78
|
summary: Ruby CSS parser.
|
85
|
-
test_files:
|
79
|
+
test_files:
|
86
80
|
- test/test_css_parser_basic.rb
|
87
81
|
- test/test_css_parser_loading.rb
|
88
82
|
- test/test_css_parser_media_types.rb
|