css_parser 1.7.0 → 1.11.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/css_parser/parser.rb +95 -99
- data/lib/css_parser/regexps.rb +53 -32
- data/lib/css_parser/rule_set.rb +375 -242
- data/lib/css_parser/version.rb +3 -1
- data/lib/css_parser.rb +23 -33
- metadata +120 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 351dd37c4f67732bcc07deb8d86c00ff6d15931787a3c08880ee8b4321fdf27e
|
4
|
+
data.tar.gz: bc830e26b6b15e06af401f0a1e9abed835e2deec34cd2986c428baca1d51d18b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0013771da7b78cf5f9ef4edb4af039a535d4b9e8709d68808159a27df9f6d76649f6976f4bbecace5773e5b7b1d1fc3e3a7afbd85be267765d5beee2ac565877
|
7
|
+
data.tar.gz: 7ef404fe368409bb2951764e232b2364522b76a5c72f171c4560f5ba70a3c51b0d149709a9e4777ae2bc8049241358b6b382005fdc18298ea0d8b24085476a15
|
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,10 @@ 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
|
+
capture_offsets: false}.merge(options)
|
42
43
|
|
43
44
|
# array of RuleSets
|
44
45
|
@rules = []
|
@@ -70,21 +71,20 @@ module CssParser
|
|
70
71
|
# Returns an array of declarations.
|
71
72
|
def find_by_selector(selector, media_types = :all)
|
72
73
|
out = []
|
73
|
-
each_selector(media_types) do |sel, dec,
|
74
|
+
each_selector(media_types) do |sel, dec, _spec|
|
74
75
|
out << dec if sel.strip == selector.strip
|
75
76
|
end
|
76
77
|
out
|
77
78
|
end
|
78
|
-
|
79
|
+
alias [] find_by_selector
|
79
80
|
|
80
81
|
# Finds the rule sets that match the given selectors
|
81
82
|
def find_rule_sets(selectors, media_types = :all)
|
82
83
|
rule_sets = []
|
83
84
|
|
84
85
|
selectors.each do |selector|
|
85
|
-
selector.gsub
|
86
|
-
|
87
|
-
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|
|
88
88
|
if !rule_sets.member?(rule_set) && rule_set.selectors.member?(selector)
|
89
89
|
rule_sets << rule_set
|
90
90
|
end
|
@@ -115,9 +115,9 @@ module CssParser
|
|
115
115
|
# parser = CssParser::Parser.new
|
116
116
|
# parser.add_block!(css)
|
117
117
|
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)}
|
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) }
|
121
121
|
|
122
122
|
block = cleanup_block(block, options)
|
123
123
|
|
@@ -129,19 +129,19 @@ module CssParser
|
|
129
129
|
if @options[:import]
|
130
130
|
block.scan(RE_AT_IMPORT_RULE).each do |import_rule|
|
131
131
|
media_types = []
|
132
|
-
if media_string = import_rule[-1]
|
133
|
-
media_string.split(
|
132
|
+
if (media_string = import_rule[-1])
|
133
|
+
media_string.split(/,/).each do |t|
|
134
134
|
media_types << CssParser.sanitize_media_query(t) unless t.empty?
|
135
135
|
end
|
136
136
|
else
|
137
137
|
media_types = [:all]
|
138
138
|
end
|
139
139
|
|
140
|
-
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?
|
141
141
|
|
142
142
|
import_path = import_rule[0].to_s.gsub(/['"]*/, '').strip
|
143
143
|
|
144
|
-
import_options = {
|
144
|
+
import_options = {media_types: media_types}
|
145
145
|
import_options[:capture_offsets] = true if options[:capture_offsets]
|
146
146
|
|
147
147
|
if options[:base_uri]
|
@@ -183,21 +183,21 @@ module CssParser
|
|
183
183
|
#
|
184
184
|
# +media_types+ can be a symbol or an array of symbols.
|
185
185
|
def add_rule_set!(ruleset, media_types = :all)
|
186
|
-
raise ArgumentError unless ruleset.
|
186
|
+
raise ArgumentError unless ruleset.is_a?(CssParser::RuleSet)
|
187
187
|
|
188
|
-
media_types = [media_types] unless Array
|
189
|
-
media_types = media_types.flat_map { |mt| CssParser.sanitize_media_query(mt)}
|
188
|
+
media_types = [media_types] unless media_types.is_a?(Array)
|
189
|
+
media_types = media_types.flat_map { |mt| CssParser.sanitize_media_query(mt) }
|
190
190
|
|
191
|
-
@rules << {:
|
191
|
+
@rules << {media_types: media_types, rules: ruleset}
|
192
192
|
end
|
193
193
|
|
194
194
|
# Remove a CssParser RuleSet object.
|
195
195
|
#
|
196
196
|
# +media_types+ can be a symbol or an array of symbols.
|
197
197
|
def remove_rule_set!(ruleset, media_types = :all)
|
198
|
-
raise ArgumentError unless ruleset.
|
198
|
+
raise ArgumentError unless ruleset.is_a?(CssParser::RuleSet)
|
199
199
|
|
200
|
-
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) }
|
201
201
|
|
202
202
|
@rules.reject! do |rule|
|
203
203
|
rule[:media_types] == media_types && rule[:rules].to_s == ruleset.to_s
|
@@ -209,7 +209,7 @@ module CssParser
|
|
209
209
|
# +media_types+ can be a symbol or an array of symbols.
|
210
210
|
def each_rule_set(media_types = :all) # :yields: rule_set, media_types
|
211
211
|
media_types = [:all] if media_types.nil?
|
212
|
-
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) }
|
213
213
|
|
214
214
|
@rules.each do |block|
|
215
215
|
if media_types.include?(:all) or block[:media_types].any? { |mt| media_types.include?(mt) }
|
@@ -222,7 +222,7 @@ module CssParser
|
|
222
222
|
def to_h(which_media = :all)
|
223
223
|
out = {}
|
224
224
|
styles_by_media_types = {}
|
225
|
-
each_selector(which_media) do |selectors, declarations,
|
225
|
+
each_selector(which_media) do |selectors, declarations, _specificity, media_types|
|
226
226
|
media_types.each do |media_type|
|
227
227
|
styles_by_media_types[media_type] ||= []
|
228
228
|
styles_by_media_types[media_type] << [selectors, declarations]
|
@@ -244,7 +244,7 @@ module CssParser
|
|
244
244
|
# +media_types+ can be a symbol or an array of symbols.
|
245
245
|
# See RuleSet#each_selector for +options+.
|
246
246
|
def each_selector(all_media_types = :all, options = {}) # :yields: selectors, declarations, specificity, media_types
|
247
|
-
return to_enum(
|
247
|
+
return to_enum(__method__, all_media_types, options) unless block_given?
|
248
248
|
|
249
249
|
each_rule_set(all_media_types) do |rule_set, media_types|
|
250
250
|
rule_set.each_selector(options) do |selectors, declarations, specificity|
|
@@ -255,9 +255,10 @@ module CssParser
|
|
255
255
|
|
256
256
|
# Output all CSS rules as a single stylesheet.
|
257
257
|
def to_s(which_media = :all)
|
258
|
-
out =
|
258
|
+
out = []
|
259
259
|
styles_by_media_types = {}
|
260
|
-
|
260
|
+
|
261
|
+
each_selector(which_media) do |selectors, declarations, _specificity, media_types|
|
261
262
|
media_types.each do |media_type|
|
262
263
|
styles_by_media_types[media_type] ||= []
|
263
264
|
styles_by_media_types[media_type] << [selectors, declarations]
|
@@ -266,20 +267,21 @@ module CssParser
|
|
266
267
|
|
267
268
|
styles_by_media_types.each_pair do |media_type, media_styles|
|
268
269
|
media_block = (media_type != :all)
|
269
|
-
out << "@media #{media_type} {
|
270
|
+
out << "@media #{media_type} {" if media_block
|
270
271
|
|
271
272
|
media_styles.each do |media_style|
|
272
273
|
if media_block
|
273
|
-
out
|
274
|
+
out.push(" #{media_style[0]} {\n #{media_style[1]}\n }")
|
274
275
|
else
|
275
|
-
out
|
276
|
+
out.push("#{media_style[0]} {\n#{media_style[1]}\n}")
|
276
277
|
end
|
277
278
|
end
|
278
279
|
|
279
|
-
out <<
|
280
|
+
out << '}' if media_block
|
280
281
|
end
|
281
282
|
|
282
|
-
out
|
283
|
+
out << ''
|
284
|
+
out.join("\n")
|
283
285
|
end
|
284
286
|
|
285
287
|
# A hash of { :media_query => rule_sets }
|
@@ -287,7 +289,7 @@ module CssParser
|
|
287
289
|
rules_by_media = {}
|
288
290
|
@rules.each do |block|
|
289
291
|
block[:media_types].each do |mt|
|
290
|
-
unless rules_by_media.
|
292
|
+
unless rules_by_media.key?(mt)
|
291
293
|
rules_by_media[mt] = []
|
292
294
|
end
|
293
295
|
rules_by_media[mt] << block[:rules]
|
@@ -299,15 +301,13 @@ module CssParser
|
|
299
301
|
|
300
302
|
# Merge declarations with the same selector.
|
301
303
|
def compact! # :nodoc:
|
302
|
-
|
303
|
-
|
304
|
-
compacted
|
304
|
+
[]
|
305
305
|
end
|
306
306
|
|
307
307
|
def parse_block_into_rule_sets!(block, options = {}) # :nodoc:
|
308
308
|
current_media_queries = [:all]
|
309
309
|
if options[:media_types]
|
310
|
-
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) }
|
311
311
|
end
|
312
312
|
|
313
313
|
in_declarations = 0
|
@@ -326,7 +326,7 @@ module CssParser
|
|
326
326
|
rule_start = nil
|
327
327
|
offset = nil
|
328
328
|
|
329
|
-
block.scan(/\s
|
329
|
+
block.scan(/\s+|\\{2,}|\\?[{}\s"]|.[^\s"{}\\]*/) do |token|
|
330
330
|
# save the regex offset so that we know where in the file we are
|
331
331
|
offset = Regexp.last_match.offset(0) if options[:capture_offsets]
|
332
332
|
|
@@ -349,7 +349,7 @@ module CssParser
|
|
349
349
|
current_declarations << token
|
350
350
|
|
351
351
|
if !in_string && token.include?('}')
|
352
|
-
current_declarations.gsub!(/\}
|
352
|
+
current_declarations.gsub!(/\}\s*$/, '')
|
353
353
|
|
354
354
|
in_declarations -= 1
|
355
355
|
current_declarations.strip!
|
@@ -374,7 +374,7 @@ module CssParser
|
|
374
374
|
current_media_queries = []
|
375
375
|
elsif in_at_media_rule
|
376
376
|
if token.include?('{')
|
377
|
-
block_depth
|
377
|
+
block_depth += 1
|
378
378
|
in_at_media_rule = false
|
379
379
|
in_media_block = true
|
380
380
|
current_media_queries << CssParser.sanitize_media_query(current_media_query)
|
@@ -393,38 +393,34 @@ module CssParser
|
|
393
393
|
elsif in_charset or token =~ /@charset/i
|
394
394
|
# iterate until we are out of the charset declaration
|
395
395
|
in_charset = !token.include?(';')
|
396
|
-
|
397
|
-
|
398
|
-
block_depth = block_depth - 1
|
396
|
+
elsif !in_string && token.include?('}')
|
397
|
+
block_depth -= 1
|
399
398
|
|
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
|
399
|
+
# reset the current media query scope
|
400
|
+
if in_media_block
|
401
|
+
current_media_queries = [:all]
|
402
|
+
in_media_block = false
|
416
403
|
end
|
404
|
+
elsif !in_string && token.include?('{')
|
405
|
+
current_selectors.strip!
|
406
|
+
in_declarations += 1
|
407
|
+
else
|
408
|
+
# if we are in a selector, add the token to the current selectors
|
409
|
+
current_selectors << token
|
410
|
+
|
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]+$/
|
417
413
|
end
|
418
414
|
end
|
419
415
|
|
420
416
|
# check for unclosed braces
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
add_rule!(current_selectors, current_declarations, current_media_queries)
|
426
|
-
end
|
417
|
+
return unless in_declarations > 0
|
418
|
+
|
419
|
+
unless options[:capture_offsets]
|
420
|
+
return add_rule!(current_selectors, current_declarations, current_media_queries)
|
427
421
|
end
|
422
|
+
|
423
|
+
add_rule_with_offsets!(current_selectors, current_declarations, options[:filename], (rule_start..offset.last), current_media_queries)
|
428
424
|
end
|
429
425
|
|
430
426
|
# Load a remote CSS file.
|
@@ -437,7 +433,7 @@ module CssParser
|
|
437
433
|
def load_uri!(uri, options = {}, deprecated = nil)
|
438
434
|
uri = Addressable::URI.parse(uri) unless uri.respond_to? :scheme
|
439
435
|
|
440
|
-
opts = {:
|
436
|
+
opts = {base_uri: nil, media_types: :all}
|
441
437
|
|
442
438
|
if options.is_a? Hash
|
443
439
|
opts.merge!(options)
|
@@ -457,14 +453,13 @@ module CssParser
|
|
457
453
|
opts[:filename] = uri.to_s if opts[:capture_offsets]
|
458
454
|
|
459
455
|
src, = read_remote_file(uri) # skip charset
|
460
|
-
|
461
|
-
|
462
|
-
end
|
456
|
+
|
457
|
+
add_block!(src, opts) if src
|
463
458
|
end
|
464
459
|
|
465
460
|
# Load a local CSS file.
|
466
461
|
def load_file!(file_name, options = {}, deprecated = nil)
|
467
|
-
opts = {:
|
462
|
+
opts = {base_dir: nil, media_types: :all}
|
468
463
|
|
469
464
|
if options.is_a? Hash
|
470
465
|
opts.merge!(options)
|
@@ -487,7 +482,7 @@ module CssParser
|
|
487
482
|
|
488
483
|
# Load a local CSS string.
|
489
484
|
def load_string!(src, options = {}, deprecated = nil)
|
490
|
-
opts = {:
|
485
|
+
opts = {base_dir: nil, media_types: :all}
|
491
486
|
|
492
487
|
if options.is_a? Hash
|
493
488
|
opts.merge!(options)
|
@@ -499,9 +494,8 @@ module CssParser
|
|
499
494
|
add_block!(src, opts)
|
500
495
|
end
|
501
496
|
|
502
|
-
|
503
|
-
|
504
497
|
protected
|
498
|
+
|
505
499
|
# Check that a path hasn't been loaded already
|
506
500
|
#
|
507
501
|
# Raises a CircularReferenceError exception if io_exceptions are on,
|
@@ -510,10 +504,11 @@ module CssParser
|
|
510
504
|
path = path.to_s
|
511
505
|
if @loaded_uris.include?(path)
|
512
506
|
raise CircularReferenceError, "can't load #{path} more than once" if @options[:io_exceptions]
|
513
|
-
|
507
|
+
|
508
|
+
false
|
514
509
|
else
|
515
510
|
@loaded_uris << path
|
516
|
-
|
511
|
+
true
|
517
512
|
end
|
518
513
|
end
|
519
514
|
|
@@ -533,7 +528,7 @@ module CssParser
|
|
533
528
|
# Returns a string.
|
534
529
|
def cleanup_block(block, options = {}) # :nodoc:
|
535
530
|
# Strip CSS comments
|
536
|
-
utf8_block = block.encode('UTF-8', '
|
531
|
+
utf8_block = block.encode('UTF-8', 'UTF-8', invalid: :replace, undef: :replace, replace: ' ')
|
537
532
|
utf8_block = ignore_pattern(utf8_block, STRIP_CSS_COMMENTS_RX, options)
|
538
533
|
|
539
534
|
# Strip HTML comments - they shouldn't really be in here but
|
@@ -541,7 +536,7 @@ module CssParser
|
|
541
536
|
utf8_block = ignore_pattern(utf8_block, STRIP_HTML_COMMENTS_RX, options)
|
542
537
|
|
543
538
|
# Strip lines containing just whitespace
|
544
|
-
utf8_block.gsub!(/^\s+$/,
|
539
|
+
utf8_block.gsub!(/^\s+$/, '') unless options[:capture_offsets]
|
545
540
|
|
546
541
|
utf8_block
|
547
542
|
end
|
@@ -577,11 +572,8 @@ module CssParser
|
|
577
572
|
if uri.scheme == 'file'
|
578
573
|
# local file
|
579
574
|
path = uri.path
|
580
|
-
path.gsub!(
|
581
|
-
|
582
|
-
src = fh.read
|
583
|
-
charset = fh.respond_to?(:charset) ? fh.charset : 'utf-8'
|
584
|
-
fh.close
|
575
|
+
path.gsub!(%r{^/}, '') if Gem.win_platform?
|
576
|
+
src = File.read(path, mode: 'rb')
|
585
577
|
else
|
586
578
|
# remote file
|
587
579
|
if uri.scheme == 'https'
|
@@ -599,21 +591,22 @@ module CssParser
|
|
599
591
|
|
600
592
|
if res.code.to_i >= 400
|
601
593
|
@redirect_count = nil
|
602
|
-
raise RemoteFileError
|
594
|
+
raise RemoteFileError, uri.to_s if @options[:io_exceptions]
|
595
|
+
|
603
596
|
return '', nil
|
604
597
|
elsif res.code.to_i >= 300 and res.code.to_i < 400
|
605
|
-
|
598
|
+
unless res['Location'].nil?
|
606
599
|
return read_remote_file Addressable::URI.parse(Addressable::URI.escape(res['Location']))
|
607
600
|
end
|
608
601
|
end
|
609
602
|
|
610
603
|
case res['content-encoding']
|
611
|
-
|
612
|
-
|
613
|
-
|
614
|
-
|
615
|
-
|
616
|
-
|
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)
|
617
610
|
end
|
618
611
|
end
|
619
612
|
|
@@ -627,15 +620,17 @@ module CssParser
|
|
627
620
|
end
|
628
621
|
rescue
|
629
622
|
@redirect_count = nil
|
630
|
-
raise RemoteFileError
|
623
|
+
raise RemoteFileError, uri.to_s if @options[:io_exceptions]
|
624
|
+
|
631
625
|
return nil, nil
|
632
626
|
end
|
633
627
|
|
634
628
|
@redirect_count = nil
|
635
|
-
|
629
|
+
[src, charset]
|
636
630
|
end
|
637
631
|
|
638
632
|
private
|
633
|
+
|
639
634
|
# Save a folded declaration block to the internal cache.
|
640
635
|
def save_folded_declaration(block_hash, folded_declaration) # :nodoc:
|
641
636
|
@folded_declaration_cache[block_hash] = folded_declaration
|
@@ -643,7 +638,7 @@ module CssParser
|
|
643
638
|
|
644
639
|
# Retrieve a folded declaration block from the internal cache.
|
645
640
|
def get_folded_declaration(block_hash) # :nodoc:
|
646
|
-
|
641
|
+
@folded_declaration_cache[block_hash] ||= nil
|
647
642
|
end
|
648
643
|
|
649
644
|
def reset! # :nodoc:
|
@@ -657,14 +652,15 @@ module CssParser
|
|
657
652
|
# passed hash
|
658
653
|
def css_node_to_h(hash, key, val)
|
659
654
|
hash[key.strip] = '' and return hash if val.nil?
|
655
|
+
|
660
656
|
lines = val.split(';')
|
661
657
|
nodes = {}
|
662
658
|
lines.each do |line|
|
663
659
|
parts = line.split(':', 2)
|
664
|
-
if
|
660
|
+
if parts[1] =~ /:/
|
665
661
|
nodes[parts[0]] = css_node_to_h(hash, parts[0], parts[1])
|
666
662
|
else
|
667
|
-
nodes[parts[0].to_s.strip] =parts[1].to_s.strip
|
663
|
+
nodes[parts[0].to_s.strip] = parts[1].to_s.strip
|
668
664
|
end
|
669
665
|
end
|
670
666
|
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, 'n')
|
11
|
+
RE_NON_ASCII = Regexp.new('([\x00-\xFF])', Regexp::IGNORECASE, 'n') # [^\0-\177]
|
10
12
|
RE_UNICODE = Regexp.new('(\\\\[0-9a-f]{1,6}(\r\n|[ \n\r\t\f])*)', Regexp::IGNORECASE | Regexp::EXTENDED | Regexp::MULTILINE, 'n')
|
11
13
|
RE_ESCAPE = Regexp.union(RE_UNICODE, '|(\\\\[^\n\r\f0-9a-f])')
|
12
14
|
RE_IDENT = Regexp.new("[\-]?([_a-z]|#{RE_NON_ASCII}|#{RE_ESCAPE})([_a-z0-9\-]|#{RE_NON_ASCII}|#{RE_ESCAPE})*", Regexp::IGNORECASE, 'n')
|
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
|