css_parser 1.5.0.pre2 → 1.8.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/lib/css_parser.rb +28 -36
- data/lib/css_parser/parser.rb +119 -118
- data/lib/css_parser/regexps.rb +38 -35
- data/lib/css_parser/rule_set.rb +373 -259
- data/lib/css_parser/version.rb +3 -1
- metadata +122 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: c049fe40a4b774b56e00c790cf18b78c8a94298185435471893cedff65a79c17
|
4
|
+
data.tar.gz: fd0600e1859e8bd188a12df4ccd252a1f75f4ae2f98707249c7750e622d5e2b4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 53b2f435c485d393778679d3202943a99b83607af4ce5edac9a15aac4461fb252371b43d98b7bf1886254b143da9bff5a61492d7a36e74e44e77b44f8ba38318
|
7
|
+
data.tar.gz: ab5fb0504811ebd05eb09e830447f832b67aad5e5559dd4be9e562c20d5bb4cc786165eb961b39867e8316c72ce90015de16e1857154784b4bf0fc700b92a34b
|
data/lib/css_parser.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'addressable/uri'
|
2
4
|
require 'uri'
|
3
5
|
require 'net/https'
|
@@ -12,7 +14,6 @@ require 'css_parser/regexps'
|
|
12
14
|
require 'css_parser/parser'
|
13
15
|
|
14
16
|
module CssParser
|
15
|
-
|
16
17
|
# Merge multiple CSS RuleSets by cascading according to the CSS 2.1 cascading rules
|
17
18
|
# (http://www.w3.org/TR/REC-CSS2/cascade.html#cascading-order).
|
18
19
|
#
|
@@ -55,10 +56,10 @@ module CssParser
|
|
55
56
|
@folded_declaration_cache = {}
|
56
57
|
|
57
58
|
# in case called like CssParser.merge([rule_set, rule_set])
|
58
|
-
rule_sets.flatten! if rule_sets[0].
|
59
|
+
rule_sets.flatten! if rule_sets[0].is_a?(Array)
|
59
60
|
|
60
|
-
unless rule_sets.all? {|rs| rs.
|
61
|
-
raise ArgumentError,
|
61
|
+
unless rule_sets.all? { |rs| rs.is_a?(CssParser::RuleSet) }
|
62
|
+
raise ArgumentError, 'all parameters must be CssParser::RuleSets.'
|
62
63
|
end
|
63
64
|
|
64
65
|
return rule_sets[0] if rule_sets.length == 1
|
@@ -70,38 +71,27 @@ module CssParser
|
|
70
71
|
rule_set.expand_shorthand!
|
71
72
|
|
72
73
|
specificity = rule_set.specificity
|
73
|
-
|
74
|
-
if rule_set.selectors.length == 0
|
75
|
-
specificity = 0
|
76
|
-
else
|
77
|
-
specificity = rule_set.selectors.map { |s| calculate_specificity(s) }.compact.max || 0
|
78
|
-
end
|
79
|
-
end
|
74
|
+
specificity ||= rule_set.selectors.map { |s| calculate_specificity(s) }.compact.max || 0
|
80
75
|
|
81
76
|
rule_set.each_declaration do |property, value, is_important|
|
82
77
|
# Add the property to the list to be folded per http://www.w3.org/TR/CSS21/cascade.html#cascading-order
|
83
|
-
if not properties.
|
84
|
-
properties[property] = {:
|
78
|
+
if not properties.key?(property)
|
79
|
+
properties[property] = {value: value, specificity: specificity, is_important: is_important}
|
85
80
|
elsif is_important
|
86
81
|
if not properties[property][:is_important] or properties[property][:specificity] <= specificity
|
87
|
-
properties[property] = {:
|
82
|
+
properties[property] = {value: value, specificity: specificity, is_important: is_important}
|
88
83
|
end
|
89
84
|
elsif properties[property][:specificity] < specificity or properties[property][:specificity] == specificity
|
90
85
|
unless properties[property][:is_important]
|
91
|
-
properties[property] = {:
|
86
|
+
properties[property] = {value: value, specificity: specificity, is_important: is_important}
|
92
87
|
end
|
93
88
|
end
|
94
|
-
|
89
|
+
end
|
95
90
|
end
|
96
91
|
|
97
|
-
merged = RuleSet.new(nil, nil)
|
98
|
-
|
99
|
-
|
100
|
-
if details[:is_important]
|
101
|
-
merged[property.strip] = details[:value].strip.gsub(/\;\Z/, '') + '!important'
|
102
|
-
else
|
103
|
-
merged[property.strip] = details[:value].strip
|
104
|
-
end
|
92
|
+
merged = properties.each_with_object(RuleSet.new(nil, nil)) do |(property, details), rule_set|
|
93
|
+
value = details[:value].strip
|
94
|
+
rule_set[property.strip] = details[:is_important] ? "#{value.gsub(/;\Z/, '')}!important" : value
|
105
95
|
end
|
106
96
|
|
107
97
|
merged.create_shorthand!
|
@@ -122,12 +112,12 @@ module CssParser
|
|
122
112
|
def self.calculate_specificity(selector)
|
123
113
|
a = 0
|
124
114
|
b = selector.scan(/\#/).length
|
125
|
-
c = selector.scan(
|
126
|
-
d = selector.scan(
|
115
|
+
c = selector.scan(NON_ID_ATTRIBUTES_AND_PSEUDO_CLASSES_RX_NC).length
|
116
|
+
d = selector.scan(ELEMENTS_AND_PSEUDO_ELEMENTS_RX_NC).length
|
127
117
|
|
128
|
-
|
118
|
+
"#{a}#{b}#{c}#{d}".to_i
|
129
119
|
rescue
|
130
|
-
|
120
|
+
0
|
131
121
|
end
|
132
122
|
|
133
123
|
# Make <tt>url()</tt> links absolute.
|
@@ -144,23 +134,25 @@ module CssParser
|
|
144
134
|
# "http://example.org/style/basic.css").inspect
|
145
135
|
# => "body { background: url('http://example.org/style/yellow.png?abc=123') };"
|
146
136
|
def self.convert_uris(css, base_uri)
|
147
|
-
base_uri = Addressable::URI.parse(base_uri) unless base_uri.
|
137
|
+
base_uri = Addressable::URI.parse(base_uri) unless base_uri.is_a?(Addressable::URI)
|
148
138
|
|
149
139
|
css.gsub(URI_RX) do
|
150
|
-
uri =
|
151
|
-
uri.gsub!(/["']+/, '')
|
140
|
+
uri = Regexp.last_match(1).to_s.gsub(/["']+/, '')
|
152
141
|
# Don't process URLs that are already absolute
|
153
|
-
unless uri
|
142
|
+
unless uri.match(%r{^[a-z]+://}i)
|
154
143
|
begin
|
155
|
-
uri = base_uri
|
156
|
-
rescue
|
144
|
+
uri = base_uri.join(uri)
|
145
|
+
rescue
|
146
|
+
nil
|
147
|
+
end
|
157
148
|
end
|
158
|
-
"url('#{uri
|
149
|
+
"url('#{uri}')"
|
159
150
|
end
|
160
151
|
end
|
161
152
|
|
162
153
|
def self.sanitize_media_query(raw)
|
163
|
-
mq = raw.to_s.gsub(
|
154
|
+
mq = raw.to_s.gsub(/\s+/, ' ')
|
155
|
+
mq.strip!
|
164
156
|
mq = 'all' if mq.empty?
|
165
157
|
mq.to_sym
|
166
158
|
end
|
data/lib/css_parser/parser.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module CssParser
|
2
4
|
# Exception class used for any errors encountered while downloading remote files.
|
3
5
|
class RemoteFileError < IOError; end
|
@@ -14,13 +16,13 @@ module CssParser
|
|
14
16
|
# [<tt>import</tt>] Follow <tt>@import</tt> rules. Boolean, default is <tt>true</tt>.
|
15
17
|
# [<tt>io_exceptions</tt>] Throw an exception if a link can not be found. Boolean, default is <tt>true</tt>.
|
16
18
|
class Parser
|
17
|
-
USER_AGENT
|
19
|
+
USER_AGENT = "Ruby CSS Parser/#{CssParser::VERSION} (https://github.com/premailer/css_parser)"
|
18
20
|
|
19
|
-
STRIP_CSS_COMMENTS_RX =
|
20
|
-
STRIP_HTML_COMMENTS_RX =
|
21
|
+
STRIP_CSS_COMMENTS_RX = %r{/\*.*?\*/}m.freeze
|
22
|
+
STRIP_HTML_COMMENTS_RX = /<!--|-->/m.freeze
|
21
23
|
|
22
24
|
# Initial parsing
|
23
|
-
RE_AT_IMPORT_RULE =
|
25
|
+
RE_AT_IMPORT_RULE = /@import\s*(?:url\s*)?(?:\()?(?:\s*)["']?([^'"\s)]*)["']?\)?([\w\s,^\]()]*)\)?[;\n]?/.freeze
|
24
26
|
|
25
27
|
MAX_REDIRECTS = 3
|
26
28
|
|
@@ -34,10 +36,10 @@ module CssParser
|
|
34
36
|
class << self; attr_reader :folded_declaration_cache; end
|
35
37
|
|
36
38
|
def initialize(options = {})
|
37
|
-
@options = {:
|
38
|
-
:
|
39
|
-
:
|
40
|
-
:
|
39
|
+
@options = {absolute_paths: false,
|
40
|
+
import: true,
|
41
|
+
io_exceptions: true,
|
42
|
+
capture_offsets: false}.merge(options)
|
41
43
|
|
42
44
|
# array of RuleSets
|
43
45
|
@rules = []
|
@@ -69,21 +71,20 @@ module CssParser
|
|
69
71
|
# Returns an array of declarations.
|
70
72
|
def find_by_selector(selector, media_types = :all)
|
71
73
|
out = []
|
72
|
-
each_selector(media_types) do |sel, dec,
|
74
|
+
each_selector(media_types) do |sel, dec, _spec|
|
73
75
|
out << dec if sel.strip == selector.strip
|
74
76
|
end
|
75
77
|
out
|
76
78
|
end
|
77
|
-
|
79
|
+
alias [] find_by_selector
|
78
80
|
|
79
81
|
# Finds the rule sets that match the given selectors
|
80
82
|
def find_rule_sets(selectors, media_types = :all)
|
81
83
|
rule_sets = []
|
82
84
|
|
83
85
|
selectors.each do |selector|
|
84
|
-
selector.gsub
|
85
|
-
|
86
|
-
each_rule_set(media_types) do |rule_set, media_type|
|
86
|
+
selector = selector.gsub(/\s+/, ' ').strip
|
87
|
+
each_rule_set(media_types) do |rule_set, _media_type|
|
87
88
|
if !rule_sets.member?(rule_set) && rule_set.selectors.member?(selector)
|
88
89
|
rule_sets << rule_set
|
89
90
|
end
|
@@ -114,9 +115,9 @@ module CssParser
|
|
114
115
|
# parser = CssParser::Parser.new
|
115
116
|
# parser.add_block!(css)
|
116
117
|
def add_block!(block, options = {})
|
117
|
-
options = {:
|
118
|
-
options[:media_types] = [options[:media_types]].flatten.collect { |mt| CssParser.sanitize_media_query(mt)}
|
119
|
-
options[:only_media_types] = [options[:only_media_types]].flatten.collect { |mt| CssParser.sanitize_media_query(mt)}
|
118
|
+
options = {base_uri: nil, base_dir: nil, charset: nil, media_types: :all, only_media_types: :all}.merge(options)
|
119
|
+
options[:media_types] = [options[:media_types]].flatten.collect { |mt| CssParser.sanitize_media_query(mt) }
|
120
|
+
options[:only_media_types] = [options[:only_media_types]].flatten.collect { |mt| CssParser.sanitize_media_query(mt) }
|
120
121
|
|
121
122
|
block = cleanup_block(block, options)
|
122
123
|
|
@@ -128,19 +129,19 @@ module CssParser
|
|
128
129
|
if @options[:import]
|
129
130
|
block.scan(RE_AT_IMPORT_RULE).each do |import_rule|
|
130
131
|
media_types = []
|
131
|
-
if media_string = import_rule[-1]
|
132
|
-
media_string.split(
|
132
|
+
if (media_string = import_rule[-1])
|
133
|
+
media_string.split(/,/).each do |t|
|
133
134
|
media_types << CssParser.sanitize_media_query(t) unless t.empty?
|
134
135
|
end
|
135
136
|
else
|
136
137
|
media_types = [:all]
|
137
138
|
end
|
138
139
|
|
139
|
-
next unless options[:only_media_types].include?(:all) or media_types.
|
140
|
+
next unless options[:only_media_types].include?(:all) or media_types.empty? or !(media_types & options[:only_media_types]).empty?
|
140
141
|
|
141
142
|
import_path = import_rule[0].to_s.gsub(/['"]*/, '').strip
|
142
143
|
|
143
|
-
import_options = {
|
144
|
+
import_options = {media_types: media_types}
|
144
145
|
import_options[:capture_offsets] = true if options[:capture_offsets]
|
145
146
|
|
146
147
|
if options[:base_uri]
|
@@ -182,20 +183,21 @@ module CssParser
|
|
182
183
|
#
|
183
184
|
# +media_types+ can be a symbol or an array of symbols.
|
184
185
|
def add_rule_set!(ruleset, media_types = :all)
|
185
|
-
raise ArgumentError unless ruleset.
|
186
|
+
raise ArgumentError unless ruleset.is_a?(CssParser::RuleSet)
|
186
187
|
|
187
|
-
media_types = [media_types]
|
188
|
+
media_types = [media_types] unless media_types.is_a?(Array)
|
189
|
+
media_types = media_types.flat_map { |mt| CssParser.sanitize_media_query(mt) }
|
188
190
|
|
189
|
-
@rules << {:
|
191
|
+
@rules << {media_types: media_types, rules: ruleset}
|
190
192
|
end
|
191
193
|
|
192
194
|
# Remove a CssParser RuleSet object.
|
193
195
|
#
|
194
196
|
# +media_types+ can be a symbol or an array of symbols.
|
195
197
|
def remove_rule_set!(ruleset, media_types = :all)
|
196
|
-
raise ArgumentError unless ruleset.
|
198
|
+
raise ArgumentError unless ruleset.is_a?(CssParser::RuleSet)
|
197
199
|
|
198
|
-
media_types = [media_types].flatten.collect { |mt| CssParser.sanitize_media_query(mt)}
|
200
|
+
media_types = [media_types].flatten.collect { |mt| CssParser.sanitize_media_query(mt) }
|
199
201
|
|
200
202
|
@rules.reject! do |rule|
|
201
203
|
rule[:media_types] == media_types && rule[:rules].to_s == ruleset.to_s
|
@@ -207,7 +209,7 @@ module CssParser
|
|
207
209
|
# +media_types+ can be a symbol or an array of symbols.
|
208
210
|
def each_rule_set(media_types = :all) # :yields: rule_set, media_types
|
209
211
|
media_types = [:all] if media_types.nil?
|
210
|
-
media_types = [media_types].flatten.collect { |mt| CssParser.sanitize_media_query(mt)}
|
212
|
+
media_types = [media_types].flatten.collect { |mt| CssParser.sanitize_media_query(mt) }
|
211
213
|
|
212
214
|
@rules.each do |block|
|
213
215
|
if media_types.include?(:all) or block[:media_types].any? { |mt| media_types.include?(mt) }
|
@@ -220,7 +222,7 @@ module CssParser
|
|
220
222
|
def to_h(which_media = :all)
|
221
223
|
out = {}
|
222
224
|
styles_by_media_types = {}
|
223
|
-
each_selector(which_media) do |selectors, declarations,
|
225
|
+
each_selector(which_media) do |selectors, declarations, _specificity, media_types|
|
224
226
|
media_types.each do |media_type|
|
225
227
|
styles_by_media_types[media_type] ||= []
|
226
228
|
styles_by_media_types[media_type] << [selectors, declarations]
|
@@ -242,6 +244,8 @@ module CssParser
|
|
242
244
|
# +media_types+ can be a symbol or an array of symbols.
|
243
245
|
# See RuleSet#each_selector for +options+.
|
244
246
|
def each_selector(all_media_types = :all, options = {}) # :yields: selectors, declarations, specificity, media_types
|
247
|
+
return to_enum(__method__, all_media_types, options) unless block_given?
|
248
|
+
|
245
249
|
each_rule_set(all_media_types) do |rule_set, media_types|
|
246
250
|
rule_set.each_selector(options) do |selectors, declarations, specificity|
|
247
251
|
yield selectors, declarations, specificity, media_types
|
@@ -251,9 +255,10 @@ module CssParser
|
|
251
255
|
|
252
256
|
# Output all CSS rules as a single stylesheet.
|
253
257
|
def to_s(which_media = :all)
|
254
|
-
out =
|
258
|
+
out = []
|
255
259
|
styles_by_media_types = {}
|
256
|
-
|
260
|
+
|
261
|
+
each_selector(which_media) do |selectors, declarations, _specificity, media_types|
|
257
262
|
media_types.each do |media_type|
|
258
263
|
styles_by_media_types[media_type] ||= []
|
259
264
|
styles_by_media_types[media_type] << [selectors, declarations]
|
@@ -262,20 +267,21 @@ module CssParser
|
|
262
267
|
|
263
268
|
styles_by_media_types.each_pair do |media_type, media_styles|
|
264
269
|
media_block = (media_type != :all)
|
265
|
-
out
|
270
|
+
out << "@media #{media_type} {" if media_block
|
266
271
|
|
267
272
|
media_styles.each do |media_style|
|
268
273
|
if media_block
|
269
|
-
out
|
274
|
+
out.push(" #{media_style[0]} {\n #{media_style[1]}\n }")
|
270
275
|
else
|
271
|
-
out
|
276
|
+
out.push("#{media_style[0]} {\n#{media_style[1]}\n}")
|
272
277
|
end
|
273
278
|
end
|
274
279
|
|
275
|
-
out
|
280
|
+
out << '}' if media_block
|
276
281
|
end
|
277
282
|
|
278
|
-
out
|
283
|
+
out << ''
|
284
|
+
out.join("\n")
|
279
285
|
end
|
280
286
|
|
281
287
|
# A hash of { :media_query => rule_sets }
|
@@ -283,7 +289,7 @@ module CssParser
|
|
283
289
|
rules_by_media = {}
|
284
290
|
@rules.each do |block|
|
285
291
|
block[:media_types].each do |mt|
|
286
|
-
unless rules_by_media.
|
292
|
+
unless rules_by_media.key?(mt)
|
287
293
|
rules_by_media[mt] = []
|
288
294
|
end
|
289
295
|
rules_by_media[mt] << block[:rules]
|
@@ -295,15 +301,13 @@ module CssParser
|
|
295
301
|
|
296
302
|
# Merge declarations with the same selector.
|
297
303
|
def compact! # :nodoc:
|
298
|
-
|
299
|
-
|
300
|
-
compacted
|
304
|
+
[]
|
301
305
|
end
|
302
306
|
|
303
307
|
def parse_block_into_rule_sets!(block, options = {}) # :nodoc:
|
304
308
|
current_media_queries = [:all]
|
305
309
|
if options[:media_types]
|
306
|
-
current_media_queries = options[:media_types].flatten.collect { |mt| CssParser.sanitize_media_query(mt)}
|
310
|
+
current_media_queries = options[:media_types].flatten.collect { |mt| CssParser.sanitize_media_query(mt) }
|
307
311
|
end
|
308
312
|
|
309
313
|
in_declarations = 0
|
@@ -314,44 +318,43 @@ module CssParser
|
|
314
318
|
in_at_media_rule = false
|
315
319
|
in_media_block = false
|
316
320
|
|
317
|
-
current_selectors =
|
318
|
-
current_media_query =
|
319
|
-
current_declarations =
|
321
|
+
current_selectors = String.new
|
322
|
+
current_media_query = String.new
|
323
|
+
current_declarations = String.new
|
320
324
|
|
321
325
|
# once we are in a rule, we will use this to store where we started if we are capturing offsets
|
322
326
|
rule_start = nil
|
323
327
|
offset = nil
|
324
328
|
|
325
|
-
block.scan(
|
326
|
-
token = matches[0]
|
327
|
-
|
329
|
+
block.scan(/\s+|\\{2,}|\\?[{}\s"]|.[^\s"{}\\]*/) do |token|
|
328
330
|
# save the regex offset so that we know where in the file we are
|
329
331
|
offset = Regexp.last_match.offset(0) if options[:capture_offsets]
|
330
332
|
|
331
|
-
if token
|
333
|
+
if token.start_with?('"') # found un-escaped double quote
|
332
334
|
in_string = !in_string
|
333
335
|
end
|
334
336
|
|
335
337
|
if in_declarations > 0
|
336
338
|
# too deep, malformed declaration block
|
337
339
|
if in_declarations > 1
|
338
|
-
in_declarations -= 1 if token
|
340
|
+
in_declarations -= 1 if token.include?('}')
|
339
341
|
next
|
340
342
|
end
|
341
343
|
|
342
|
-
if
|
344
|
+
if !in_string && token.include?('{')
|
343
345
|
in_declarations += 1
|
344
346
|
next
|
345
347
|
end
|
346
348
|
|
347
|
-
current_declarations
|
349
|
+
current_declarations << token
|
348
350
|
|
349
|
-
if
|
350
|
-
current_declarations.gsub!(/\}
|
351
|
+
if !in_string && token.include?('}')
|
352
|
+
current_declarations.gsub!(/\}\s*$/, '')
|
351
353
|
|
352
354
|
in_declarations -= 1
|
355
|
+
current_declarations.strip!
|
353
356
|
|
354
|
-
unless current_declarations.
|
357
|
+
unless current_declarations.empty?
|
355
358
|
if options[:capture_offsets]
|
356
359
|
add_rule_with_offsets!(current_selectors, current_declarations, options[:filename], (rule_start..offset.last), current_media_queries)
|
357
360
|
else
|
@@ -359,8 +362,8 @@ module CssParser
|
|
359
362
|
end
|
360
363
|
end
|
361
364
|
|
362
|
-
current_selectors =
|
363
|
-
current_declarations =
|
365
|
+
current_selectors = String.new
|
366
|
+
current_declarations = String.new
|
364
367
|
|
365
368
|
# restart our search for selectors and declarations
|
366
369
|
rule_start = nil if options[:capture_offsets]
|
@@ -370,56 +373,54 @@ module CssParser
|
|
370
373
|
in_at_media_rule = true
|
371
374
|
current_media_queries = []
|
372
375
|
elsif in_at_media_rule
|
373
|
-
if token
|
374
|
-
block_depth
|
376
|
+
if token.include?('{')
|
377
|
+
block_depth += 1
|
375
378
|
in_at_media_rule = false
|
376
379
|
in_media_block = true
|
377
380
|
current_media_queries << CssParser.sanitize_media_query(current_media_query)
|
378
|
-
current_media_query =
|
379
|
-
elsif token
|
381
|
+
current_media_query = String.new
|
382
|
+
elsif token.include?(',')
|
380
383
|
# new media query begins
|
381
|
-
token.
|
382
|
-
|
384
|
+
token.tr!(',', ' ')
|
385
|
+
token.strip!
|
386
|
+
current_media_query << token << ' '
|
383
387
|
current_media_queries << CssParser.sanitize_media_query(current_media_query)
|
384
|
-
current_media_query =
|
388
|
+
current_media_query = String.new
|
385
389
|
else
|
386
|
-
|
390
|
+
token.strip!
|
391
|
+
current_media_query << token << ' '
|
387
392
|
end
|
388
393
|
elsif in_charset or token =~ /@charset/i
|
389
394
|
# iterate until we are out of the charset declaration
|
390
|
-
in_charset =
|
395
|
+
in_charset = !token.include?(';')
|
396
|
+
elsif !in_string && token.include?('}')
|
397
|
+
block_depth -= 1
|
398
|
+
|
399
|
+
# reset the current media query scope
|
400
|
+
if in_media_block
|
401
|
+
current_media_queries = [:all]
|
402
|
+
in_media_block = false
|
403
|
+
end
|
404
|
+
elsif !in_string && token.include?('{')
|
405
|
+
current_selectors.strip!
|
406
|
+
in_declarations += 1
|
391
407
|
else
|
392
|
-
if token
|
393
|
-
|
408
|
+
# if we are in a selector, add the token to the current selectors
|
409
|
+
current_selectors << token
|
394
410
|
|
395
|
-
|
396
|
-
|
397
|
-
current_media_queries = [:all]
|
398
|
-
in_media_block = false
|
399
|
-
end
|
400
|
-
else
|
401
|
-
if token =~ /\{/ and not in_string
|
402
|
-
current_selectors.strip!
|
403
|
-
in_declarations += 1
|
404
|
-
else
|
405
|
-
# if we are in a selector, add the token to the current selectors
|
406
|
-
current_selectors += token
|
407
|
-
|
408
|
-
# mark this as the beginning of the selector unless we have already marked it
|
409
|
-
rule_start = offset.first if options[:capture_offsets] && rule_start.nil? && token =~ /^[^\s]+$/
|
410
|
-
end
|
411
|
-
end
|
411
|
+
# mark this as the beginning of the selector unless we have already marked it
|
412
|
+
rule_start = offset.first if options[:capture_offsets] && rule_start.nil? && token =~ /^[^\s]+$/
|
412
413
|
end
|
413
414
|
end
|
414
415
|
|
415
416
|
# check for unclosed braces
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
add_rule!(current_selectors, current_declarations, current_media_queries)
|
421
|
-
end
|
417
|
+
return unless in_declarations > 0
|
418
|
+
|
419
|
+
unless options[:capture_offsets]
|
420
|
+
return add_rule!(current_selectors, current_declarations, current_media_queries)
|
422
421
|
end
|
422
|
+
|
423
|
+
add_rule_with_offsets!(current_selectors, current_declarations, options[:filename], (rule_start..offset.last), current_media_queries)
|
423
424
|
end
|
424
425
|
|
425
426
|
# Load a remote CSS file.
|
@@ -432,7 +433,7 @@ module CssParser
|
|
432
433
|
def load_uri!(uri, options = {}, deprecated = nil)
|
433
434
|
uri = Addressable::URI.parse(uri) unless uri.respond_to? :scheme
|
434
435
|
|
435
|
-
opts = {:
|
436
|
+
opts = {base_uri: nil, media_types: :all}
|
436
437
|
|
437
438
|
if options.is_a? Hash
|
438
439
|
opts.merge!(options)
|
@@ -452,14 +453,13 @@ module CssParser
|
|
452
453
|
opts[:filename] = uri.to_s if opts[:capture_offsets]
|
453
454
|
|
454
455
|
src, = read_remote_file(uri) # skip charset
|
455
|
-
|
456
|
-
|
457
|
-
end
|
456
|
+
|
457
|
+
add_block!(src, opts) if src
|
458
458
|
end
|
459
459
|
|
460
460
|
# Load a local CSS file.
|
461
461
|
def load_file!(file_name, options = {}, deprecated = nil)
|
462
|
-
opts = {:
|
462
|
+
opts = {base_dir: nil, media_types: :all}
|
463
463
|
|
464
464
|
if options.is_a? Hash
|
465
465
|
opts.merge!(options)
|
@@ -482,7 +482,7 @@ module CssParser
|
|
482
482
|
|
483
483
|
# Load a local CSS string.
|
484
484
|
def load_string!(src, options = {}, deprecated = nil)
|
485
|
-
opts = {:
|
485
|
+
opts = {base_dir: nil, media_types: :all}
|
486
486
|
|
487
487
|
if options.is_a? Hash
|
488
488
|
opts.merge!(options)
|
@@ -494,9 +494,8 @@ module CssParser
|
|
494
494
|
add_block!(src, opts)
|
495
495
|
end
|
496
496
|
|
497
|
-
|
498
|
-
|
499
497
|
protected
|
498
|
+
|
500
499
|
# Check that a path hasn't been loaded already
|
501
500
|
#
|
502
501
|
# Raises a CircularReferenceError exception if io_exceptions are on,
|
@@ -505,10 +504,11 @@ module CssParser
|
|
505
504
|
path = path.to_s
|
506
505
|
if @loaded_uris.include?(path)
|
507
506
|
raise CircularReferenceError, "can't load #{path} more than once" if @options[:io_exceptions]
|
508
|
-
|
507
|
+
|
508
|
+
false
|
509
509
|
else
|
510
510
|
@loaded_uris << path
|
511
|
-
|
511
|
+
true
|
512
512
|
end
|
513
513
|
end
|
514
514
|
|
@@ -528,7 +528,7 @@ module CssParser
|
|
528
528
|
# Returns a string.
|
529
529
|
def cleanup_block(block, options = {}) # :nodoc:
|
530
530
|
# Strip CSS comments
|
531
|
-
utf8_block = block.encode('UTF-8', '
|
531
|
+
utf8_block = block.encode('UTF-8', 'UTF-8', invalid: :replace, undef: :replace, replace: ' ')
|
532
532
|
utf8_block = ignore_pattern(utf8_block, STRIP_CSS_COMMENTS_RX, options)
|
533
533
|
|
534
534
|
# Strip HTML comments - they shouldn't really be in here but
|
@@ -536,7 +536,7 @@ module CssParser
|
|
536
536
|
utf8_block = ignore_pattern(utf8_block, STRIP_HTML_COMMENTS_RX, options)
|
537
537
|
|
538
538
|
# Strip lines containing just whitespace
|
539
|
-
utf8_block.gsub!(/^\s+$/,
|
539
|
+
utf8_block.gsub!(/^\s+$/, '') unless options[:capture_offsets]
|
540
540
|
|
541
541
|
utf8_block
|
542
542
|
end
|
@@ -572,11 +572,8 @@ module CssParser
|
|
572
572
|
if uri.scheme == 'file'
|
573
573
|
# local file
|
574
574
|
path = uri.path
|
575
|
-
path.gsub!(
|
576
|
-
|
577
|
-
src = fh.read
|
578
|
-
charset = fh.respond_to?(:charset) ? fh.charset : 'utf-8'
|
579
|
-
fh.close
|
575
|
+
path.gsub!(%r{^/}, '') if Gem.win_platform?
|
576
|
+
src = File.read(path, mode: 'rb')
|
580
577
|
else
|
581
578
|
# remote file
|
582
579
|
if uri.scheme == 'https'
|
@@ -594,21 +591,22 @@ module CssParser
|
|
594
591
|
|
595
592
|
if res.code.to_i >= 400
|
596
593
|
@redirect_count = nil
|
597
|
-
raise RemoteFileError
|
594
|
+
raise RemoteFileError, uri.to_s if @options[:io_exceptions]
|
595
|
+
|
598
596
|
return '', nil
|
599
597
|
elsif res.code.to_i >= 300 and res.code.to_i < 400
|
600
|
-
|
598
|
+
unless res['Location'].nil?
|
601
599
|
return read_remote_file Addressable::URI.parse(Addressable::URI.escape(res['Location']))
|
602
600
|
end
|
603
601
|
end
|
604
602
|
|
605
603
|
case res['content-encoding']
|
606
|
-
|
607
|
-
|
608
|
-
|
609
|
-
|
610
|
-
|
611
|
-
|
604
|
+
when 'gzip'
|
605
|
+
io = Zlib::GzipReader.new(StringIO.new(res.body))
|
606
|
+
src = io.read
|
607
|
+
when 'deflate'
|
608
|
+
io = Zlib::Inflate.new
|
609
|
+
src = io.inflate(res.body)
|
612
610
|
end
|
613
611
|
end
|
614
612
|
|
@@ -622,15 +620,17 @@ module CssParser
|
|
622
620
|
end
|
623
621
|
rescue
|
624
622
|
@redirect_count = nil
|
625
|
-
raise RemoteFileError
|
623
|
+
raise RemoteFileError, uri.to_s if @options[:io_exceptions]
|
624
|
+
|
626
625
|
return nil, nil
|
627
626
|
end
|
628
627
|
|
629
628
|
@redirect_count = nil
|
630
|
-
|
629
|
+
[src, charset]
|
631
630
|
end
|
632
631
|
|
633
632
|
private
|
633
|
+
|
634
634
|
# Save a folded declaration block to the internal cache.
|
635
635
|
def save_folded_declaration(block_hash, folded_declaration) # :nodoc:
|
636
636
|
@folded_declaration_cache[block_hash] = folded_declaration
|
@@ -638,7 +638,7 @@ module CssParser
|
|
638
638
|
|
639
639
|
# Retrieve a folded declaration block from the internal cache.
|
640
640
|
def get_folded_declaration(block_hash) # :nodoc:
|
641
|
-
|
641
|
+
@folded_declaration_cache[block_hash] ||= nil
|
642
642
|
end
|
643
643
|
|
644
644
|
def reset! # :nodoc:
|
@@ -652,14 +652,15 @@ module CssParser
|
|
652
652
|
# passed hash
|
653
653
|
def css_node_to_h(hash, key, val)
|
654
654
|
hash[key.strip] = '' and return hash if val.nil?
|
655
|
+
|
655
656
|
lines = val.split(';')
|
656
657
|
nodes = {}
|
657
658
|
lines.each do |line|
|
658
659
|
parts = line.split(':', 2)
|
659
|
-
if
|
660
|
+
if parts[1] =~ /:/
|
660
661
|
nodes[parts[0]] = css_node_to_h(hash, parts[0], parts[1])
|
661
662
|
else
|
662
|
-
nodes[parts[0].to_s.strip] =parts[1].to_s.strip
|
663
|
+
nodes[parts[0].to_s.strip] = parts[1].to_s.strip
|
663
664
|
end
|
664
665
|
end
|
665
666
|
hash[key.strip] = nodes
|