css_parser 1.1.9 → 1.2.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/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
|