css_parser 1.5.0 → 1.9.0
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.
- 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 +40 -35
- data/lib/css_parser/rule_set.rb +387 -266
- data/lib/css_parser/version.rb +3 -1
- metadata +120 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: ecd734b97658689e087ca19039ea51377c23d9e1c4be6099b084006150255fce
|
4
|
+
data.tar.gz: d5a326fac6e3b57fe75fa3bc664ae8511a0e0dcf34f80dd5736ee8cc9709f5ee
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ca0d59324036c9071c5f957235f10022d3db4841d4e4782e147f30e2cde1a1cd46873ede55db01b260676fc7289c0e7c4ad7e0e6ac7dbd0b38a1de8aa13333a1
|
7
|
+
data.tar.gz: da5fb20229ecfac93b0436a0ed6954c109908429628cd635751c58fd9b3d029227da06a60bb80f5f4bd2fac55851ac258e7ff59d09fea59e93157f38d21c0fac
|
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
|