css_parser 1.7.1 → 1.13.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 +4 -4
- data/lib/css_parser/parser.rb +97 -98
- data/lib/css_parser/regexps.rb +55 -34
- data/lib/css_parser/rule_set.rb +377 -242
- data/lib/css_parser/version.rb +3 -1
- data/lib/css_parser.rb +23 -33
- metadata +116 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9bad6c42b4f2fbda14292b503c3f381ea4b3a9a4b77c74dd44da724866237901
|
4
|
+
data.tar.gz: 0b47ed4ef7d197a5552af3a99db42b89a8937cca205c7b6cf865cfb930d2d265
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e91f481e093e423b873fcfd673bda8337645b066a7c278bea28175f8e11d94d9ac1dc401fc7defa1f8afca11d9125f7007aa8c12e1a4410351511f6e876bf593
|
7
|
+
data.tar.gz: 0fd7db359a459575bc2b7f08b5d6f86d5d78c58247af82db87884e5a1e10e768f9f3f0247abf2d491ebf30c9df8ec623fd65c8c51bf089fa29eba9cf75a9d9ac
|
data/lib/css_parser/parser.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
module CssParser
|
3
4
|
# Exception class used for any errors encountered while downloading remote files.
|
4
5
|
class RemoteFileError < IOError; end
|
@@ -15,13 +16,13 @@ module CssParser
|
|
15
16
|
# [<tt>import</tt>] Follow <tt>@import</tt> rules. Boolean, default is <tt>true</tt>.
|
16
17
|
# [<tt>io_exceptions</tt>] Throw an exception if a link can not be found. Boolean, default is <tt>true</tt>.
|
17
18
|
class Parser
|
18
|
-
USER_AGENT
|
19
|
+
USER_AGENT = "Ruby CSS Parser/#{CssParser::VERSION} (https://github.com/premailer/css_parser)"
|
19
20
|
|
20
|
-
STRIP_CSS_COMMENTS_RX =
|
21
|
-
STRIP_HTML_COMMENTS_RX =
|
21
|
+
STRIP_CSS_COMMENTS_RX = %r{/\*.*?\*/}m.freeze
|
22
|
+
STRIP_HTML_COMMENTS_RX = /<!--|-->/m.freeze
|
22
23
|
|
23
24
|
# Initial parsing
|
24
|
-
RE_AT_IMPORT_RULE =
|
25
|
+
RE_AT_IMPORT_RULE = /@import\s*(?:url\s*)?(?:\()?(?:\s*)["']?([^'"\s)]*)["']?\)?([\w\s,^\]()]*)\)?[;\n]?/.freeze
|
25
26
|
|
26
27
|
MAX_REDIRECTS = 3
|
27
28
|
|
@@ -35,10 +36,11 @@ module CssParser
|
|
35
36
|
class << self; attr_reader :folded_declaration_cache; end
|
36
37
|
|
37
38
|
def initialize(options = {})
|
38
|
-
@options = {:
|
39
|
-
:
|
40
|
-
:
|
41
|
-
:
|
39
|
+
@options = {absolute_paths: false,
|
40
|
+
import: true,
|
41
|
+
io_exceptions: true,
|
42
|
+
rule_set_exceptions: true,
|
43
|
+
capture_offsets: false}.merge(options)
|
42
44
|
|
43
45
|
# array of RuleSets
|
44
46
|
@rules = []
|
@@ -70,21 +72,20 @@ module CssParser
|
|
70
72
|
# Returns an array of declarations.
|
71
73
|
def find_by_selector(selector, media_types = :all)
|
72
74
|
out = []
|
73
|
-
each_selector(media_types) do |sel, dec,
|
75
|
+
each_selector(media_types) do |sel, dec, _spec|
|
74
76
|
out << dec if sel.strip == selector.strip
|
75
77
|
end
|
76
78
|
out
|
77
79
|
end
|
78
|
-
|
80
|
+
alias [] find_by_selector
|
79
81
|
|
80
82
|
# Finds the rule sets that match the given selectors
|
81
83
|
def find_rule_sets(selectors, media_types = :all)
|
82
84
|
rule_sets = []
|
83
85
|
|
84
86
|
selectors.each do |selector|
|
85
|
-
selector.gsub
|
86
|
-
|
87
|
-
each_rule_set(media_types) do |rule_set, media_type|
|
87
|
+
selector = selector.gsub(/\s+/, ' ').strip
|
88
|
+
each_rule_set(media_types) do |rule_set, _media_type|
|
88
89
|
if !rule_sets.member?(rule_set) && rule_set.selectors.member?(selector)
|
89
90
|
rule_sets << rule_set
|
90
91
|
end
|
@@ -115,9 +116,9 @@ module CssParser
|
|
115
116
|
# parser = CssParser::Parser.new
|
116
117
|
# parser.add_block!(css)
|
117
118
|
def add_block!(block, options = {})
|
118
|
-
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)}
|
119
|
+
options = {base_uri: nil, base_dir: nil, charset: nil, media_types: :all, only_media_types: :all}.merge(options)
|
120
|
+
options[:media_types] = [options[:media_types]].flatten.collect { |mt| CssParser.sanitize_media_query(mt) }
|
121
|
+
options[:only_media_types] = [options[:only_media_types]].flatten.collect { |mt| CssParser.sanitize_media_query(mt) }
|
121
122
|
|
122
123
|
block = cleanup_block(block, options)
|
123
124
|
|
@@ -129,19 +130,19 @@ module CssParser
|
|
129
130
|
if @options[:import]
|
130
131
|
block.scan(RE_AT_IMPORT_RULE).each do |import_rule|
|
131
132
|
media_types = []
|
132
|
-
if media_string = import_rule[-1]
|
133
|
-
media_string.split(
|
133
|
+
if (media_string = import_rule[-1])
|
134
|
+
media_string.split(/,/).each do |t|
|
134
135
|
media_types << CssParser.sanitize_media_query(t) unless t.empty?
|
135
136
|
end
|
136
137
|
else
|
137
138
|
media_types = [:all]
|
138
139
|
end
|
139
140
|
|
140
|
-
next unless options[:only_media_types].include?(:all) or media_types.
|
141
|
+
next unless options[:only_media_types].include?(:all) or media_types.empty? or !(media_types & options[:only_media_types]).empty?
|
141
142
|
|
142
143
|
import_path = import_rule[0].to_s.gsub(/['"]*/, '').strip
|
143
144
|
|
144
|
-
import_options = {
|
145
|
+
import_options = {media_types: media_types}
|
145
146
|
import_options[:capture_offsets] = true if options[:capture_offsets]
|
146
147
|
|
147
148
|
if options[:base_uri]
|
@@ -167,6 +168,8 @@ module CssParser
|
|
167
168
|
def add_rule!(selectors, declarations, media_types = :all)
|
168
169
|
rule_set = RuleSet.new(selectors, declarations)
|
169
170
|
add_rule_set!(rule_set, media_types)
|
171
|
+
rescue ArgumentError => e
|
172
|
+
raise e if @options[:rule_set_exceptions]
|
170
173
|
end
|
171
174
|
|
172
175
|
# Add a CSS rule by setting the +selectors+, +declarations+, +filename+, +offset+ and +media_types+.
|
@@ -183,21 +186,21 @@ module CssParser
|
|
183
186
|
#
|
184
187
|
# +media_types+ can be a symbol or an array of symbols.
|
185
188
|
def add_rule_set!(ruleset, media_types = :all)
|
186
|
-
raise ArgumentError unless ruleset.
|
189
|
+
raise ArgumentError unless ruleset.is_a?(CssParser::RuleSet)
|
187
190
|
|
188
|
-
media_types = [media_types] unless Array
|
189
|
-
media_types = media_types.flat_map { |mt| CssParser.sanitize_media_query(mt)}
|
191
|
+
media_types = [media_types] unless media_types.is_a?(Array)
|
192
|
+
media_types = media_types.flat_map { |mt| CssParser.sanitize_media_query(mt) }
|
190
193
|
|
191
|
-
@rules << {:
|
194
|
+
@rules << {media_types: media_types, rules: ruleset}
|
192
195
|
end
|
193
196
|
|
194
197
|
# Remove a CssParser RuleSet object.
|
195
198
|
#
|
196
199
|
# +media_types+ can be a symbol or an array of symbols.
|
197
200
|
def remove_rule_set!(ruleset, media_types = :all)
|
198
|
-
raise ArgumentError unless ruleset.
|
201
|
+
raise ArgumentError unless ruleset.is_a?(CssParser::RuleSet)
|
199
202
|
|
200
|
-
media_types = [media_types].flatten.collect { |mt| CssParser.sanitize_media_query(mt)}
|
203
|
+
media_types = [media_types].flatten.collect { |mt| CssParser.sanitize_media_query(mt) }
|
201
204
|
|
202
205
|
@rules.reject! do |rule|
|
203
206
|
rule[:media_types] == media_types && rule[:rules].to_s == ruleset.to_s
|
@@ -209,7 +212,7 @@ module CssParser
|
|
209
212
|
# +media_types+ can be a symbol or an array of symbols.
|
210
213
|
def each_rule_set(media_types = :all) # :yields: rule_set, media_types
|
211
214
|
media_types = [:all] if media_types.nil?
|
212
|
-
media_types = [media_types].flatten.collect { |mt| CssParser.sanitize_media_query(mt)}
|
215
|
+
media_types = [media_types].flatten.collect { |mt| CssParser.sanitize_media_query(mt) }
|
213
216
|
|
214
217
|
@rules.each do |block|
|
215
218
|
if media_types.include?(:all) or block[:media_types].any? { |mt| media_types.include?(mt) }
|
@@ -222,7 +225,7 @@ module CssParser
|
|
222
225
|
def to_h(which_media = :all)
|
223
226
|
out = {}
|
224
227
|
styles_by_media_types = {}
|
225
|
-
each_selector(which_media) do |selectors, declarations,
|
228
|
+
each_selector(which_media) do |selectors, declarations, _specificity, media_types|
|
226
229
|
media_types.each do |media_type|
|
227
230
|
styles_by_media_types[media_type] ||= []
|
228
231
|
styles_by_media_types[media_type] << [selectors, declarations]
|
@@ -244,7 +247,7 @@ module CssParser
|
|
244
247
|
# +media_types+ can be a symbol or an array of symbols.
|
245
248
|
# See RuleSet#each_selector for +options+.
|
246
249
|
def each_selector(all_media_types = :all, options = {}) # :yields: selectors, declarations, specificity, media_types
|
247
|
-
return to_enum(
|
250
|
+
return to_enum(__method__, all_media_types, options) unless block_given?
|
248
251
|
|
249
252
|
each_rule_set(all_media_types) do |rule_set, media_types|
|
250
253
|
rule_set.each_selector(options) do |selectors, declarations, specificity|
|
@@ -255,9 +258,10 @@ module CssParser
|
|
255
258
|
|
256
259
|
# Output all CSS rules as a single stylesheet.
|
257
260
|
def to_s(which_media = :all)
|
258
|
-
out =
|
261
|
+
out = []
|
259
262
|
styles_by_media_types = {}
|
260
|
-
|
263
|
+
|
264
|
+
each_selector(which_media) do |selectors, declarations, _specificity, media_types|
|
261
265
|
media_types.each do |media_type|
|
262
266
|
styles_by_media_types[media_type] ||= []
|
263
267
|
styles_by_media_types[media_type] << [selectors, declarations]
|
@@ -266,20 +270,21 @@ module CssParser
|
|
266
270
|
|
267
271
|
styles_by_media_types.each_pair do |media_type, media_styles|
|
268
272
|
media_block = (media_type != :all)
|
269
|
-
out << "@media #{media_type} {
|
273
|
+
out << "@media #{media_type} {" if media_block
|
270
274
|
|
271
275
|
media_styles.each do |media_style|
|
272
276
|
if media_block
|
273
|
-
out
|
277
|
+
out.push(" #{media_style[0]} {\n #{media_style[1]}\n }")
|
274
278
|
else
|
275
|
-
out
|
279
|
+
out.push("#{media_style[0]} {\n#{media_style[1]}\n}")
|
276
280
|
end
|
277
281
|
end
|
278
282
|
|
279
|
-
out <<
|
283
|
+
out << '}' if media_block
|
280
284
|
end
|
281
285
|
|
282
|
-
out
|
286
|
+
out << ''
|
287
|
+
out.join("\n")
|
283
288
|
end
|
284
289
|
|
285
290
|
# A hash of { :media_query => rule_sets }
|
@@ -287,7 +292,7 @@ module CssParser
|
|
287
292
|
rules_by_media = {}
|
288
293
|
@rules.each do |block|
|
289
294
|
block[:media_types].each do |mt|
|
290
|
-
unless rules_by_media.
|
295
|
+
unless rules_by_media.key?(mt)
|
291
296
|
rules_by_media[mt] = []
|
292
297
|
end
|
293
298
|
rules_by_media[mt] << block[:rules]
|
@@ -299,15 +304,13 @@ module CssParser
|
|
299
304
|
|
300
305
|
# Merge declarations with the same selector.
|
301
306
|
def compact! # :nodoc:
|
302
|
-
|
303
|
-
|
304
|
-
compacted
|
307
|
+
[]
|
305
308
|
end
|
306
309
|
|
307
310
|
def parse_block_into_rule_sets!(block, options = {}) # :nodoc:
|
308
311
|
current_media_queries = [:all]
|
309
312
|
if options[:media_types]
|
310
|
-
current_media_queries = options[:media_types].flatten.collect { |mt| CssParser.sanitize_media_query(mt)}
|
313
|
+
current_media_queries = options[:media_types].flatten.collect { |mt| CssParser.sanitize_media_query(mt) }
|
311
314
|
end
|
312
315
|
|
313
316
|
in_declarations = 0
|
@@ -326,7 +329,7 @@ module CssParser
|
|
326
329
|
rule_start = nil
|
327
330
|
offset = nil
|
328
331
|
|
329
|
-
block.scan(/\s
|
332
|
+
block.scan(/\s+|\\{2,}|\\?[{}\s"]|.[^\s"{}\\]*/) do |token|
|
330
333
|
# save the regex offset so that we know where in the file we are
|
331
334
|
offset = Regexp.last_match.offset(0) if options[:capture_offsets]
|
332
335
|
|
@@ -349,7 +352,7 @@ module CssParser
|
|
349
352
|
current_declarations << token
|
350
353
|
|
351
354
|
if !in_string && token.include?('}')
|
352
|
-
current_declarations.gsub!(/\}
|
355
|
+
current_declarations.gsub!(/\}\s*$/, '')
|
353
356
|
|
354
357
|
in_declarations -= 1
|
355
358
|
current_declarations.strip!
|
@@ -374,7 +377,7 @@ module CssParser
|
|
374
377
|
current_media_queries = []
|
375
378
|
elsif in_at_media_rule
|
376
379
|
if token.include?('{')
|
377
|
-
block_depth
|
380
|
+
block_depth += 1
|
378
381
|
in_at_media_rule = false
|
379
382
|
in_media_block = true
|
380
383
|
current_media_queries << CssParser.sanitize_media_query(current_media_query)
|
@@ -393,38 +396,34 @@ module CssParser
|
|
393
396
|
elsif in_charset or token =~ /@charset/i
|
394
397
|
# iterate until we are out of the charset declaration
|
395
398
|
in_charset = !token.include?(';')
|
396
|
-
|
397
|
-
|
398
|
-
block_depth = block_depth - 1
|
399
|
+
elsif !in_string && token.include?('}')
|
400
|
+
block_depth -= 1
|
399
401
|
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
end
|
405
|
-
else
|
406
|
-
if !in_string && token.include?('{')
|
407
|
-
current_selectors.strip!
|
408
|
-
in_declarations += 1
|
409
|
-
else
|
410
|
-
# if we are in a selector, add the token to the current selectors
|
411
|
-
current_selectors << token
|
412
|
-
|
413
|
-
# mark this as the beginning of the selector unless we have already marked it
|
414
|
-
rule_start = offset.first if options[:capture_offsets] && rule_start.nil? && token =~ /^[^\s]+$/
|
415
|
-
end
|
402
|
+
# reset the current media query scope
|
403
|
+
if in_media_block
|
404
|
+
current_media_queries = [:all]
|
405
|
+
in_media_block = false
|
416
406
|
end
|
407
|
+
elsif !in_string && token.include?('{')
|
408
|
+
current_selectors.strip!
|
409
|
+
in_declarations += 1
|
410
|
+
else
|
411
|
+
# if we are in a selector, add the token to the current selectors
|
412
|
+
current_selectors << token
|
413
|
+
|
414
|
+
# mark this as the beginning of the selector unless we have already marked it
|
415
|
+
rule_start = offset.first if options[:capture_offsets] && rule_start.nil? && token =~ /^[^\s]+$/
|
417
416
|
end
|
418
417
|
end
|
419
418
|
|
420
419
|
# check for unclosed braces
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
add_rule!(current_selectors, current_declarations, current_media_queries)
|
426
|
-
end
|
420
|
+
return unless in_declarations > 0
|
421
|
+
|
422
|
+
unless options[:capture_offsets]
|
423
|
+
return add_rule!(current_selectors, current_declarations, current_media_queries)
|
427
424
|
end
|
425
|
+
|
426
|
+
add_rule_with_offsets!(current_selectors, current_declarations, options[:filename], (rule_start..offset.last), current_media_queries)
|
428
427
|
end
|
429
428
|
|
430
429
|
# Load a remote CSS file.
|
@@ -437,7 +436,7 @@ module CssParser
|
|
437
436
|
def load_uri!(uri, options = {}, deprecated = nil)
|
438
437
|
uri = Addressable::URI.parse(uri) unless uri.respond_to? :scheme
|
439
438
|
|
440
|
-
opts = {:
|
439
|
+
opts = {base_uri: nil, media_types: :all}
|
441
440
|
|
442
441
|
if options.is_a? Hash
|
443
442
|
opts.merge!(options)
|
@@ -457,14 +456,13 @@ module CssParser
|
|
457
456
|
opts[:filename] = uri.to_s if opts[:capture_offsets]
|
458
457
|
|
459
458
|
src, = read_remote_file(uri) # skip charset
|
460
|
-
|
461
|
-
|
462
|
-
end
|
459
|
+
|
460
|
+
add_block!(src, opts) if src
|
463
461
|
end
|
464
462
|
|
465
463
|
# Load a local CSS file.
|
466
464
|
def load_file!(file_name, options = {}, deprecated = nil)
|
467
|
-
opts = {:
|
465
|
+
opts = {base_dir: nil, media_types: :all}
|
468
466
|
|
469
467
|
if options.is_a? Hash
|
470
468
|
opts.merge!(options)
|
@@ -487,7 +485,7 @@ module CssParser
|
|
487
485
|
|
488
486
|
# Load a local CSS string.
|
489
487
|
def load_string!(src, options = {}, deprecated = nil)
|
490
|
-
opts = {:
|
488
|
+
opts = {base_dir: nil, media_types: :all}
|
491
489
|
|
492
490
|
if options.is_a? Hash
|
493
491
|
opts.merge!(options)
|
@@ -499,9 +497,8 @@ module CssParser
|
|
499
497
|
add_block!(src, opts)
|
500
498
|
end
|
501
499
|
|
502
|
-
|
503
|
-
|
504
500
|
protected
|
501
|
+
|
505
502
|
# Check that a path hasn't been loaded already
|
506
503
|
#
|
507
504
|
# Raises a CircularReferenceError exception if io_exceptions are on,
|
@@ -510,10 +507,11 @@ module CssParser
|
|
510
507
|
path = path.to_s
|
511
508
|
if @loaded_uris.include?(path)
|
512
509
|
raise CircularReferenceError, "can't load #{path} more than once" if @options[:io_exceptions]
|
513
|
-
|
510
|
+
|
511
|
+
false
|
514
512
|
else
|
515
513
|
@loaded_uris << path
|
516
|
-
|
514
|
+
true
|
517
515
|
end
|
518
516
|
end
|
519
517
|
|
@@ -541,7 +539,7 @@ module CssParser
|
|
541
539
|
utf8_block = ignore_pattern(utf8_block, STRIP_HTML_COMMENTS_RX, options)
|
542
540
|
|
543
541
|
# Strip lines containing just whitespace
|
544
|
-
utf8_block.gsub!(/^\s+$/,
|
542
|
+
utf8_block.gsub!(/^\s+$/, '') unless options[:capture_offsets]
|
545
543
|
|
546
544
|
utf8_block
|
547
545
|
end
|
@@ -577,11 +575,8 @@ module CssParser
|
|
577
575
|
if uri.scheme == 'file'
|
578
576
|
# local file
|
579
577
|
path = uri.path
|
580
|
-
path.gsub!(
|
581
|
-
|
582
|
-
src = fh.read
|
583
|
-
charset = fh.respond_to?(:charset) ? fh.charset : 'utf-8'
|
584
|
-
fh.close
|
578
|
+
path.gsub!(%r{^/}, '') if Gem.win_platform?
|
579
|
+
src = File.read(path, mode: 'rb')
|
585
580
|
else
|
586
581
|
# remote file
|
587
582
|
if uri.scheme == 'https'
|
@@ -599,21 +594,22 @@ module CssParser
|
|
599
594
|
|
600
595
|
if res.code.to_i >= 400
|
601
596
|
@redirect_count = nil
|
602
|
-
raise RemoteFileError
|
597
|
+
raise RemoteFileError, uri.to_s if @options[:io_exceptions]
|
598
|
+
|
603
599
|
return '', nil
|
604
600
|
elsif res.code.to_i >= 300 and res.code.to_i < 400
|
605
|
-
|
601
|
+
unless res['Location'].nil?
|
606
602
|
return read_remote_file Addressable::URI.parse(Addressable::URI.escape(res['Location']))
|
607
603
|
end
|
608
604
|
end
|
609
605
|
|
610
606
|
case res['content-encoding']
|
611
|
-
|
612
|
-
|
613
|
-
|
614
|
-
|
615
|
-
|
616
|
-
|
607
|
+
when 'gzip'
|
608
|
+
io = Zlib::GzipReader.new(StringIO.new(res.body))
|
609
|
+
src = io.read
|
610
|
+
when 'deflate'
|
611
|
+
io = Zlib::Inflate.new
|
612
|
+
src = io.inflate(res.body)
|
617
613
|
end
|
618
614
|
end
|
619
615
|
|
@@ -627,15 +623,17 @@ module CssParser
|
|
627
623
|
end
|
628
624
|
rescue
|
629
625
|
@redirect_count = nil
|
630
|
-
raise RemoteFileError
|
626
|
+
raise RemoteFileError, uri.to_s if @options[:io_exceptions]
|
627
|
+
|
631
628
|
return nil, nil
|
632
629
|
end
|
633
630
|
|
634
631
|
@redirect_count = nil
|
635
|
-
|
632
|
+
[src, charset]
|
636
633
|
end
|
637
634
|
|
638
635
|
private
|
636
|
+
|
639
637
|
# Save a folded declaration block to the internal cache.
|
640
638
|
def save_folded_declaration(block_hash, folded_declaration) # :nodoc:
|
641
639
|
@folded_declaration_cache[block_hash] = folded_declaration
|
@@ -643,7 +641,7 @@ module CssParser
|
|
643
641
|
|
644
642
|
# Retrieve a folded declaration block from the internal cache.
|
645
643
|
def get_folded_declaration(block_hash) # :nodoc:
|
646
|
-
|
644
|
+
@folded_declaration_cache[block_hash] ||= nil
|
647
645
|
end
|
648
646
|
|
649
647
|
def reset! # :nodoc:
|
@@ -657,14 +655,15 @@ module CssParser
|
|
657
655
|
# passed hash
|
658
656
|
def css_node_to_h(hash, key, val)
|
659
657
|
hash[key.strip] = '' and return hash if val.nil?
|
658
|
+
|
660
659
|
lines = val.split(';')
|
661
660
|
nodes = {}
|
662
661
|
lines.each do |line|
|
663
662
|
parts = line.split(':', 2)
|
664
|
-
if
|
663
|
+
if parts[1] =~ /:/
|
665
664
|
nodes[parts[0]] = css_node_to_h(hash, parts[0], parts[1])
|
666
665
|
else
|
667
|
-
nodes[parts[0].to_s.strip] =parts[1].to_s.strip
|
666
|
+
nodes[parts[0].to_s.strip] = parts[1].to_s.strip
|
668
667
|
end
|
669
668
|
end
|
670
669
|
hash[key.strip] = nodes
|
data/lib/css_parser/regexps.rb
CHANGED
@@ -1,65 +1,86 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module CssParser
|
2
|
-
def self.regex_possible_values
|
4
|
+
def self.regex_possible_values(*values)
|
3
5
|
Regexp.new("([\s]*^)?(#{values.join('|')})([\s]*$)?", 'i')
|
4
6
|
end
|
5
7
|
|
6
8
|
# :stopdoc:
|
7
9
|
# Base types
|
8
10
|
RE_NL = Regexp.new('(\n|\r\n|\r|\f)')
|
9
|
-
RE_NON_ASCII = Regexp.new('([\x00-\xFF])', Regexp::IGNORECASE
|
10
|
-
RE_UNICODE = Regexp.new('(\\\\[0-9a-f]{1,6}(\r\n|[ \n\r\t\f])*)', Regexp::IGNORECASE | Regexp::EXTENDED | Regexp::MULTILINE
|
11
|
+
RE_NON_ASCII = Regexp.new('([\x00-\xFF])', Regexp::IGNORECASE | Regexp::NOENCODING) # [^\0-\177]
|
12
|
+
RE_UNICODE = Regexp.new('(\\\\[0-9a-f]{1,6}(\r\n|[ \n\r\t\f])*)', Regexp::IGNORECASE | Regexp::EXTENDED | Regexp::MULTILINE | Regexp::NOENCODING)
|
11
13
|
RE_ESCAPE = Regexp.union(RE_UNICODE, '|(\\\\[^\n\r\f0-9a-f])')
|
12
|
-
RE_IDENT = Regexp.new("[\-]?([_a-z]|#{RE_NON_ASCII}|#{RE_ESCAPE})([_a-z0-9\-]|#{RE_NON_ASCII}|#{RE_ESCAPE})*", Regexp::IGNORECASE
|
14
|
+
RE_IDENT = Regexp.new("[\-]?([_a-z]|#{RE_NON_ASCII}|#{RE_ESCAPE})([_a-z0-9\-]|#{RE_NON_ASCII}|#{RE_ESCAPE})*", Regexp::IGNORECASE | Regexp::NOENCODING)
|
13
15
|
|
14
16
|
# General strings
|
15
|
-
RE_STRING1 =
|
16
|
-
RE_STRING2 =
|
17
|
+
RE_STRING1 = /("(.[^\n\r\f"]*|\\#{RE_NL}|#{RE_ESCAPE})*")/.freeze
|
18
|
+
RE_STRING2 = /('(.[^\n\r\f']*|\\#{RE_NL}|#{RE_ESCAPE})*')/.freeze
|
17
19
|
RE_STRING = Regexp.union(RE_STRING1, RE_STRING2)
|
18
20
|
|
19
21
|
RE_INHERIT = regex_possible_values 'inherit'
|
20
22
|
|
21
|
-
RE_URI =
|
22
|
-
URI_RX = /url\(("([^"]*)"|'([^']*)'|([^)]*))\)/im
|
23
|
-
|
23
|
+
RE_URI = /(url\(\s*(\s*#{RE_STRING}\s*)\s*\))|(url\(\s*([!#$%&*\-~]|#{RE_NON_ASCII}|#{RE_ESCAPE})*\s*)\)/ixm.freeze
|
24
|
+
URI_RX = /url\(("([^"]*)"|'([^']*)'|([^)]*))\)/im.freeze
|
25
|
+
URI_RX_OR_NONE = Regexp.union(URI_RX, /none/i)
|
26
|
+
RE_GRADIENT = /[-a-z]*gradient\([-a-z0-9 .,#%()]*\)/im.freeze
|
24
27
|
|
25
28
|
# Initial parsing
|
26
|
-
RE_AT_IMPORT_RULE =
|
29
|
+
RE_AT_IMPORT_RULE = /@import\s+(url\()?["']?(.[^'"\s]*)["']?\)?([\w\s,^\])]*)\)?;?/.freeze
|
27
30
|
|
28
31
|
#--
|
29
|
-
#RE_AT_MEDIA_RULE = Regexp.new('(\"(.[^\n\r\f\\"]*|\\\\' + RE_NL.to_s + '|' + RE_ESCAPE.to_s + ')*\")')
|
32
|
+
# RE_AT_MEDIA_RULE = Regexp.new('(\"(.[^\n\r\f\\"]*|\\\\' + RE_NL.to_s + '|' + RE_ESCAPE.to_s + ')*\")')
|
30
33
|
|
31
|
-
#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
|
34
|
+
# 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
|
32
35
|
#++
|
33
|
-
IMPORTANT_IN_PROPERTY_RX =
|
36
|
+
IMPORTANT_IN_PROPERTY_RX = /\s*!important\b\s*/i.freeze
|
34
37
|
|
35
38
|
RE_INSIDE_OUTSIDE = regex_possible_values 'inside', 'outside'
|
36
39
|
RE_SCROLL_FIXED = regex_possible_values 'scroll', 'fixed'
|
37
40
|
RE_REPEAT = regex_possible_values 'repeat(\-x|\-y)*|no\-repeat'
|
38
|
-
RE_LIST_STYLE_TYPE = regex_possible_values
|
39
|
-
|
40
|
-
|
41
|
-
|
41
|
+
RE_LIST_STYLE_TYPE = regex_possible_values(
|
42
|
+
'disc', 'circle', 'square', 'decimal-leading-zero', 'decimal', 'lower-roman',
|
43
|
+
'upper-roman', 'lower-greek', 'lower-alpha', 'lower-latin', 'upper-alpha',
|
44
|
+
'upper-latin', 'hebrew', 'armenian', 'georgian', 'cjk-ideographic', 'hiragana',
|
45
|
+
'hira-gana-iroha', 'katakana-iroha', 'katakana', 'none'
|
46
|
+
)
|
47
|
+
RE_IMAGE = Regexp.union(CssParser::URI_RX, CssParser::RE_GRADIENT, /none/i)
|
42
48
|
|
43
|
-
STRIP_CSS_COMMENTS_RX =
|
44
|
-
STRIP_HTML_COMMENTS_RX =
|
49
|
+
STRIP_CSS_COMMENTS_RX = %r{/\*.*?\*/}m.freeze
|
50
|
+
STRIP_HTML_COMMENTS_RX = /<!--|-->/m.freeze
|
45
51
|
|
46
52
|
# Special units
|
47
|
-
BOX_MODEL_UNITS_RX = /(auto|inherit|0|(
|
53
|
+
BOX_MODEL_UNITS_RX = /(auto|inherit|0|(-*([0-9]+|[0-9]*\.[0-9]+)(rem|vw|vh|vm|vmin|vmax|e[mx]+|px|[cm]+m|p[tc+]|in|%)))([\s;]|\Z)/imx.freeze
|
48
54
|
RE_LENGTH_OR_PERCENTAGE = Regexp.new('([\-]*(([0-9]*\.[0-9]+)|[0-9]+)(e[mx]+|px|[cm]+m|p[tc+]|in|\%))', Regexp::IGNORECASE)
|
49
55
|
RE_BACKGROUND_POSITION = Regexp.new("((((#{RE_LENGTH_OR_PERCENTAGE})|left|center|right|top|bottom)[\s]*){1,2})", Regexp::IGNORECASE | Regexp::EXTENDED)
|
50
56
|
RE_BACKGROUND_SIZE = Regexp.new("\\s*/\\s*((((#{RE_LENGTH_OR_PERCENTAGE})|auto|cover|contain|initial|inherit)[\\s]*){1,2})", Regexp::IGNORECASE | Regexp::EXTENDED)
|
51
|
-
FONT_UNITS_RX = /((
|
52
|
-
RE_BORDER_STYLE = /(
|
57
|
+
FONT_UNITS_RX = /((x+-)*small|medium|larger*|auto|inherit|([0-9]+|[0-9]*\.[0-9]+)(e[mx]+|px|[cm]+m|p[tc+]|in|%)*)/i.freeze
|
58
|
+
RE_BORDER_STYLE = /(\s*^)?(none|hidden|dotted|dashed|solid|double|dot-dash|dot-dot-dash|wave|groove|ridge|inset|outset)(\s*$)?/imx.freeze
|
53
59
|
RE_BORDER_UNITS = Regexp.union(BOX_MODEL_UNITS_RX, /(thin|medium|thick)/i)
|
54
60
|
|
61
|
+
# Functions like calc, var, clamp, etc.
|
62
|
+
RE_FUNCTIONS = /
|
63
|
+
(
|
64
|
+
[a-z0-9-]+ # function name
|
65
|
+
)
|
66
|
+
(?>
|
67
|
+
\( # opening parenthesis
|
68
|
+
(?:
|
69
|
+
([^()]+)
|
70
|
+
| # recursion via subexpression
|
71
|
+
\g<0>
|
72
|
+
)*
|
73
|
+
\) # closing parenthesis
|
74
|
+
)
|
75
|
+
/imx.freeze
|
55
76
|
|
56
77
|
# Patterns for specificity calculations
|
57
|
-
NON_ID_ATTRIBUTES_AND_PSEUDO_CLASSES_RX_NC= /
|
58
|
-
(
|
78
|
+
NON_ID_ATTRIBUTES_AND_PSEUDO_CLASSES_RX_NC = /
|
79
|
+
(?:\.\w+) # classes
|
59
80
|
|
|
60
81
|
\[(?:\w+) # attributes
|
61
82
|
|
|
62
|
-
(
|
83
|
+
(?::(?: # pseudo classes
|
63
84
|
link|visited|active
|
64
85
|
|hover|focus
|
65
86
|
|lang
|
@@ -71,16 +92,16 @@ module CssParser
|
|
71
92
|
|only-child|only-of-type
|
72
93
|
|empty|contains
|
73
94
|
))
|
74
|
-
/ix
|
95
|
+
/ix.freeze
|
75
96
|
ELEMENTS_AND_PSEUDO_ELEMENTS_RX_NC = /
|
76
|
-
(?:(?:^|[\s
|
97
|
+
(?:(?:^|[\s+>~]+)\w+ # elements
|
77
98
|
|
|
78
|
-
|
99
|
+
:{1,2}(?: # pseudo-elements
|
79
100
|
after|before
|
80
101
|
|first-letter|first-line
|
81
102
|
|selection
|
82
103
|
)
|
83
|
-
)/ix
|
104
|
+
)/ix.freeze
|
84
105
|
|
85
106
|
# Colours
|
86
107
|
NAMED_COLOURS = %w[
|
@@ -235,11 +256,11 @@ module CssParser
|
|
235
256
|
transparent
|
236
257
|
inherit
|
237
258
|
currentColor
|
238
|
-
]
|
239
|
-
RE_COLOUR_NUMERIC = /\b(hsl|rgb)\s*\(-?\s*-?\d+(\.\d+)?%?\s*%?,-?\s*-?\d+(\.\d+)?%?\s*%?,-?\s*-?\d+(\.\d+)?%?\s*%?\)/i
|
240
|
-
RE_COLOUR_NUMERIC_ALPHA = /\b(hsla|rgba)\s*\(-?\s*-?\d+(\.\d+)?%?\s*%?,-?\s*-?\d+(\.\d+)?%?\s*%?,-?\s*-?\d+(\.\d+)?%?\s*%?,-?\s*-?\d+(\.\d+)?%?\s*%?\)/i
|
241
|
-
RE_COLOUR_HEX = /\s*#([0-9a-fA-F]{6}|[0-9a-fA-F]{3})\b
|
242
|
-
RE_COLOUR_NAMED = /\s*\b(#{NAMED_COLOURS.join('|')})\b/i
|
259
|
+
].freeze
|
260
|
+
RE_COLOUR_NUMERIC = /\b(hsl|rgb)\s*\(-?\s*-?\d+(\.\d+)?%?\s*%?,-?\s*-?\d+(\.\d+)?%?\s*%?,-?\s*-?\d+(\.\d+)?%?\s*%?\)/i.freeze
|
261
|
+
RE_COLOUR_NUMERIC_ALPHA = /\b(hsla|rgba)\s*\(-?\s*-?\d+(\.\d+)?%?\s*%?,-?\s*-?\d+(\.\d+)?%?\s*%?,-?\s*-?\d+(\.\d+)?%?\s*%?,-?\s*-?\d+(\.\d+)?%?\s*%?\)/i.freeze
|
262
|
+
RE_COLOUR_HEX = /\s*#([0-9a-fA-F]{6}|[0-9a-fA-F]{3})\b/.freeze
|
263
|
+
RE_COLOUR_NAMED = /\s*\b(#{NAMED_COLOURS.join('|')})\b/i.freeze
|
243
264
|
RE_COLOUR = Regexp.union(RE_COLOUR_NUMERIC, RE_COLOUR_NUMERIC_ALPHA, RE_COLOUR_HEX, RE_COLOUR_NAMED)
|
244
265
|
# :startdoc:
|
245
266
|
end
|