css_parser 1.7.1 → 1.17.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f2dafcbcaaa6e1e5ecb45b092b2d997c799d02c1c06d90130989b45cc085452d
4
- data.tar.gz: 1b5cfa1908355aa798b96ed3234e00ea26d3f4a8f36076a2f6ccdec840dfaa71
3
+ metadata.gz: 47d696725a77d514c0a1ca529dc18e39d41bdd4106a786f21219f14c2ccf43f5
4
+ data.tar.gz: eb6a39b541cfa6bd8c4d66fc4e0adb06310922794b47e582989aba6dd8f5729b
5
5
  SHA512:
6
- metadata.gz: a589901eab26c8b78d8b0c5eae5224f249ff6e30caa9021c41d583b5a530b4b926f7d3f57cefad6c7b340c9ea925cc44e6b7b86321f6cecfd27684921822b594
7
- data.tar.gz: 60882d77d15847bab9aa70ebcfc788cf959580e61577e372badf1677aacee6958eae54e91e6db8ea6063a1cc9b627525a704adb13040b7be601608b69627b0d7
6
+ metadata.gz: db5d0a6cf245796841621194c360c31f5f349596fa00a755152bd109a50707ff5dc301a47601fb09400fa00479e2319a9321f3657c9eb0d5a11b4db7c5d8b048
7
+ data.tar.gz: 7dd50eff88bc656f81928098d736fb7c657ebe6ced0328523cee6e3e778f5b2e7d285311436be6ffd210f8b122467f99dfad739828d43530db41e60a74694b25
@@ -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,12 @@ 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 = "Ruby CSS Parser/#{CssParser::VERSION} (https://github.com/premailer/css_parser)"
19
-
20
- STRIP_CSS_COMMENTS_RX = /\/\*.*?\*\//m
21
- STRIP_HTML_COMMENTS_RX = /\<\!\-\-|\-\-\>/m
19
+ USER_AGENT = "Ruby CSS Parser/#{CssParser::VERSION} (https://github.com/premailer/css_parser)"
20
+ STRIP_CSS_COMMENTS_RX = %r{/\*.*?\*/}m.freeze
21
+ STRIP_HTML_COMMENTS_RX = /<!--|-->/m.freeze
22
22
 
23
23
  # Initial parsing
24
- RE_AT_IMPORT_RULE = /\@import\s*(?:url\s*)?(?:\()?(?:\s*)["']?([^'"\s\)]*)["']?\)?([\w\s\,^\]\(\)]*)\)?[;\n]?/
24
+ RE_AT_IMPORT_RULE = /@import\s*(?:url\s*)?(?:\()?(?:\s*)["']?([^'"\s)]*)["']?\)?([\w\s,^\]()]*)\)?[;\n]?/.freeze
25
25
 
26
26
  MAX_REDIRECTS = 3
27
27
 
@@ -35,10 +35,14 @@ module CssParser
35
35
  class << self; attr_reader :folded_declaration_cache; end
36
36
 
37
37
  def initialize(options = {})
38
- @options = {:absolute_paths => false,
39
- :import => true,
40
- :io_exceptions => true,
41
- :capture_offsets => false}.merge(options)
38
+ @options = {
39
+ absolute_paths: false,
40
+ import: true,
41
+ io_exceptions: true,
42
+ rule_set_exceptions: true,
43
+ capture_offsets: false,
44
+ user_agent: USER_AGENT
45
+ }.merge(options)
42
46
 
43
47
  # array of RuleSets
44
48
  @rules = []
@@ -70,21 +74,20 @@ module CssParser
70
74
  # Returns an array of declarations.
71
75
  def find_by_selector(selector, media_types = :all)
72
76
  out = []
73
- each_selector(media_types) do |sel, dec, spec|
77
+ each_selector(media_types) do |sel, dec, _spec|
74
78
  out << dec if sel.strip == selector.strip
75
79
  end
76
80
  out
77
81
  end
78
- alias_method :[], :find_by_selector
82
+ alias [] find_by_selector
79
83
 
80
84
  # Finds the rule sets that match the given selectors
81
85
  def find_rule_sets(selectors, media_types = :all)
82
86
  rule_sets = []
83
87
 
84
88
  selectors.each do |selector|
85
- selector.gsub!(/\s+/, ' ')
86
- selector.strip!
87
- each_rule_set(media_types) do |rule_set, media_type|
89
+ selector = selector.gsub(/\s+/, ' ').strip
90
+ each_rule_set(media_types) do |rule_set, _media_type|
88
91
  if !rule_sets.member?(rule_set) && rule_set.selectors.member?(selector)
89
92
  rule_sets << rule_set
90
93
  end
@@ -115,9 +118,9 @@ module CssParser
115
118
  # parser = CssParser::Parser.new
116
119
  # parser.add_block!(css)
117
120
  def add_block!(block, options = {})
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
+ options = {base_uri: nil, base_dir: nil, charset: nil, media_types: :all, only_media_types: :all}.merge(options)
122
+ options[:media_types] = [options[:media_types]].flatten.collect { |mt| CssParser.sanitize_media_query(mt) }
123
+ options[:only_media_types] = [options[:only_media_types]].flatten.collect { |mt| CssParser.sanitize_media_query(mt) }
121
124
 
122
125
  block = cleanup_block(block, options)
123
126
 
@@ -129,19 +132,19 @@ module CssParser
129
132
  if @options[:import]
130
133
  block.scan(RE_AT_IMPORT_RULE).each do |import_rule|
131
134
  media_types = []
132
- if media_string = import_rule[-1]
133
- media_string.split(/[,]/).each do |t|
135
+ if (media_string = import_rule[-1])
136
+ media_string.split(/,/).each do |t|
134
137
  media_types << CssParser.sanitize_media_query(t) unless t.empty?
135
138
  end
136
139
  else
137
140
  media_types = [:all]
138
141
  end
139
142
 
140
- next unless options[:only_media_types].include?(:all) or media_types.length < 1 or (media_types & options[:only_media_types]).length > 0
143
+ next unless options[:only_media_types].include?(:all) or media_types.empty? or !(media_types & options[:only_media_types]).empty?
141
144
 
142
145
  import_path = import_rule[0].to_s.gsub(/['"]*/, '').strip
143
146
 
144
- import_options = { :media_types => media_types }
147
+ import_options = {media_types: media_types}
145
148
  import_options[:capture_offsets] = true if options[:capture_offsets]
146
149
 
147
150
  if options[:base_uri]
@@ -167,6 +170,8 @@ module CssParser
167
170
  def add_rule!(selectors, declarations, media_types = :all)
168
171
  rule_set = RuleSet.new(selectors, declarations)
169
172
  add_rule_set!(rule_set, media_types)
173
+ rescue ArgumentError => e
174
+ raise e if @options[:rule_set_exceptions]
170
175
  end
171
176
 
172
177
  # Add a CSS rule by setting the +selectors+, +declarations+, +filename+, +offset+ and +media_types+.
@@ -183,21 +188,21 @@ module CssParser
183
188
  #
184
189
  # +media_types+ can be a symbol or an array of symbols.
185
190
  def add_rule_set!(ruleset, media_types = :all)
186
- raise ArgumentError unless ruleset.kind_of?(CssParser::RuleSet)
191
+ raise ArgumentError unless ruleset.is_a?(CssParser::RuleSet)
187
192
 
188
- media_types = [media_types] unless Array === media_types
189
- media_types = media_types.flat_map { |mt| CssParser.sanitize_media_query(mt)}
193
+ media_types = [media_types] unless media_types.is_a?(Array)
194
+ media_types = media_types.flat_map { |mt| CssParser.sanitize_media_query(mt) }
190
195
 
191
- @rules << {:media_types => media_types, :rules => ruleset}
196
+ @rules << {media_types: media_types, rules: ruleset}
192
197
  end
193
198
 
194
199
  # Remove a CssParser RuleSet object.
195
200
  #
196
201
  # +media_types+ can be a symbol or an array of symbols.
197
202
  def remove_rule_set!(ruleset, media_types = :all)
198
- raise ArgumentError unless ruleset.kind_of?(CssParser::RuleSet)
203
+ raise ArgumentError unless ruleset.is_a?(CssParser::RuleSet)
199
204
 
200
- media_types = [media_types].flatten.collect { |mt| CssParser.sanitize_media_query(mt)}
205
+ media_types = [media_types].flatten.collect { |mt| CssParser.sanitize_media_query(mt) }
201
206
 
202
207
  @rules.reject! do |rule|
203
208
  rule[:media_types] == media_types && rule[:rules].to_s == ruleset.to_s
@@ -209,7 +214,7 @@ module CssParser
209
214
  # +media_types+ can be a symbol or an array of symbols.
210
215
  def each_rule_set(media_types = :all) # :yields: rule_set, media_types
211
216
  media_types = [:all] if media_types.nil?
212
- media_types = [media_types].flatten.collect { |mt| CssParser.sanitize_media_query(mt)}
217
+ media_types = [media_types].flatten.collect { |mt| CssParser.sanitize_media_query(mt) }
213
218
 
214
219
  @rules.each do |block|
215
220
  if media_types.include?(:all) or block[:media_types].any? { |mt| media_types.include?(mt) }
@@ -222,7 +227,7 @@ module CssParser
222
227
  def to_h(which_media = :all)
223
228
  out = {}
224
229
  styles_by_media_types = {}
225
- each_selector(which_media) do |selectors, declarations, specificity, media_types|
230
+ each_selector(which_media) do |selectors, declarations, _specificity, media_types|
226
231
  media_types.each do |media_type|
227
232
  styles_by_media_types[media_type] ||= []
228
233
  styles_by_media_types[media_type] << [selectors, declarations]
@@ -244,7 +249,7 @@ module CssParser
244
249
  # +media_types+ can be a symbol or an array of symbols.
245
250
  # See RuleSet#each_selector for +options+.
246
251
  def each_selector(all_media_types = :all, options = {}) # :yields: selectors, declarations, specificity, media_types
247
- return to_enum(:each_selector) unless block_given?
252
+ return to_enum(__method__, all_media_types, options) unless block_given?
248
253
 
249
254
  each_rule_set(all_media_types) do |rule_set, media_types|
250
255
  rule_set.each_selector(options) do |selectors, declarations, specificity|
@@ -255,9 +260,10 @@ module CssParser
255
260
 
256
261
  # Output all CSS rules as a single stylesheet.
257
262
  def to_s(which_media = :all)
258
- out = String.new
263
+ out = []
259
264
  styles_by_media_types = {}
260
- each_selector(which_media) do |selectors, declarations, specificity, media_types|
265
+
266
+ each_selector(which_media) do |selectors, declarations, _specificity, media_types|
261
267
  media_types.each do |media_type|
262
268
  styles_by_media_types[media_type] ||= []
263
269
  styles_by_media_types[media_type] << [selectors, declarations]
@@ -266,20 +272,21 @@ module CssParser
266
272
 
267
273
  styles_by_media_types.each_pair do |media_type, media_styles|
268
274
  media_block = (media_type != :all)
269
- out << "@media #{media_type} {\n" if media_block
275
+ out << "@media #{media_type} {" if media_block
270
276
 
271
277
  media_styles.each do |media_style|
272
278
  if media_block
273
- out << " #{media_style[0]} {\n #{media_style[1]}\n }\n"
279
+ out.push(" #{media_style[0]} {\n #{media_style[1]}\n }")
274
280
  else
275
- out << "#{media_style[0]} {\n#{media_style[1]}\n}\n"
281
+ out.push("#{media_style[0]} {\n#{media_style[1]}\n}")
276
282
  end
277
283
  end
278
284
 
279
- out << "}\n" if media_block
285
+ out << '}' if media_block
280
286
  end
281
287
 
282
- out
288
+ out << ''
289
+ out.join("\n")
283
290
  end
284
291
 
285
292
  # A hash of { :media_query => rule_sets }
@@ -287,7 +294,7 @@ module CssParser
287
294
  rules_by_media = {}
288
295
  @rules.each do |block|
289
296
  block[:media_types].each do |mt|
290
- unless rules_by_media.has_key?(mt)
297
+ unless rules_by_media.key?(mt)
291
298
  rules_by_media[mt] = []
292
299
  end
293
300
  rules_by_media[mt] << block[:rules]
@@ -299,15 +306,13 @@ module CssParser
299
306
 
300
307
  # Merge declarations with the same selector.
301
308
  def compact! # :nodoc:
302
- compacted = []
303
-
304
- compacted
309
+ []
305
310
  end
306
311
 
307
312
  def parse_block_into_rule_sets!(block, options = {}) # :nodoc:
308
313
  current_media_queries = [:all]
309
314
  if options[:media_types]
310
- current_media_queries = options[:media_types].flatten.collect { |mt| CssParser.sanitize_media_query(mt)}
315
+ current_media_queries = options[:media_types].flatten.collect { |mt| CssParser.sanitize_media_query(mt) }
311
316
  end
312
317
 
313
318
  in_declarations = 0
@@ -326,7 +331,7 @@ module CssParser
326
331
  rule_start = nil
327
332
  offset = nil
328
333
 
329
- block.scan(/\s+|[\\]{2,}|[\\]?[{}\s"]|.[^\s"{}\\]*/) do |token|
334
+ block.scan(/\s+|\\{2,}|\\?[{}\s"]|[()]|.[^\s"{}()\\]*/) do |token|
330
335
  # save the regex offset so that we know where in the file we are
331
336
  offset = Regexp.last_match.offset(0) if options[:capture_offsets]
332
337
 
@@ -349,7 +354,7 @@ module CssParser
349
354
  current_declarations << token
350
355
 
351
356
  if !in_string && token.include?('}')
352
- current_declarations.gsub!(/\}[\s]*$/, '')
357
+ current_declarations.gsub!(/\}\s*$/, '')
353
358
 
354
359
  in_declarations -= 1
355
360
  current_declarations.strip!
@@ -374,7 +379,7 @@ module CssParser
374
379
  current_media_queries = []
375
380
  elsif in_at_media_rule
376
381
  if token.include?('{')
377
- block_depth = block_depth + 1
382
+ block_depth += 1
378
383
  in_at_media_rule = false
379
384
  in_media_block = true
380
385
  current_media_queries << CssParser.sanitize_media_query(current_media_query)
@@ -388,43 +393,48 @@ module CssParser
388
393
  current_media_query = String.new
389
394
  else
390
395
  token.strip!
391
- current_media_query << token << ' '
396
+ # special-case the ( and ) tokens to remove inner-whitespace
397
+ # (eg we'd prefer '(width: 500px)' to '( width: 500px )' )
398
+ case token
399
+ when '('
400
+ current_media_query << token
401
+ when ')'
402
+ current_media_query.sub!(/ ?$/, token)
403
+ else
404
+ current_media_query << token << ' '
405
+ end
392
406
  end
393
407
  elsif in_charset or token =~ /@charset/i
394
408
  # iterate until we are out of the charset declaration
395
409
  in_charset = !token.include?(';')
396
- else
397
- if !in_string && token.include?('}')
398
- block_depth = block_depth - 1
399
-
400
- # reset the current media query scope
401
- if in_media_block
402
- current_media_queries = [:all]
403
- in_media_block = false
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
410
+ elsif !in_string && token.include?('}')
411
+ block_depth -= 1
412
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
413
+ # reset the current media query scope
414
+ if in_media_block
415
+ current_media_queries = [:all]
416
+ in_media_block = false
416
417
  end
418
+ elsif !in_string && token.include?('{')
419
+ current_selectors.strip!
420
+ in_declarations += 1
421
+ else
422
+ # if we are in a selector, add the token to the current selectors
423
+ current_selectors << token
424
+
425
+ # mark this as the beginning of the selector unless we have already marked it
426
+ rule_start = offset.first if options[:capture_offsets] && rule_start.nil? && token =~ /^[^\s]+$/
417
427
  end
418
428
  end
419
429
 
420
430
  # check for unclosed braces
421
- if in_declarations > 0
422
- if options[:capture_offsets]
423
- add_rule_with_offsets!(current_selectors, current_declarations, options[:filename], (rule_start..offset.last), current_media_queries)
424
- else
425
- add_rule!(current_selectors, current_declarations, current_media_queries)
426
- end
431
+ return unless in_declarations > 0
432
+
433
+ unless options[:capture_offsets]
434
+ return add_rule!(current_selectors, current_declarations, current_media_queries)
427
435
  end
436
+
437
+ add_rule_with_offsets!(current_selectors, current_declarations, options[:filename], (rule_start..offset.last), current_media_queries)
428
438
  end
429
439
 
430
440
  # Load a remote CSS file.
@@ -437,7 +447,7 @@ module CssParser
437
447
  def load_uri!(uri, options = {}, deprecated = nil)
438
448
  uri = Addressable::URI.parse(uri) unless uri.respond_to? :scheme
439
449
 
440
- opts = {:base_uri => nil, :media_types => :all}
450
+ opts = {base_uri: nil, media_types: :all}
441
451
 
442
452
  if options.is_a? Hash
443
453
  opts.merge!(options)
@@ -457,14 +467,13 @@ module CssParser
457
467
  opts[:filename] = uri.to_s if opts[:capture_offsets]
458
468
 
459
469
  src, = read_remote_file(uri) # skip charset
460
- if src
461
- add_block!(src, opts)
462
- end
470
+
471
+ add_block!(src, opts) if src
463
472
  end
464
473
 
465
474
  # Load a local CSS file.
466
475
  def load_file!(file_name, options = {}, deprecated = nil)
467
- opts = {:base_dir => nil, :media_types => :all}
476
+ opts = {base_dir: nil, media_types: :all}
468
477
 
469
478
  if options.is_a? Hash
470
479
  opts.merge!(options)
@@ -487,7 +496,7 @@ module CssParser
487
496
 
488
497
  # Load a local CSS string.
489
498
  def load_string!(src, options = {}, deprecated = nil)
490
- opts = {:base_dir => nil, :media_types => :all}
499
+ opts = {base_dir: nil, media_types: :all}
491
500
 
492
501
  if options.is_a? Hash
493
502
  opts.merge!(options)
@@ -499,9 +508,8 @@ module CssParser
499
508
  add_block!(src, opts)
500
509
  end
501
510
 
502
-
503
-
504
511
  protected
512
+
505
513
  # Check that a path hasn't been loaded already
506
514
  #
507
515
  # Raises a CircularReferenceError exception if io_exceptions are on,
@@ -510,10 +518,11 @@ module CssParser
510
518
  path = path.to_s
511
519
  if @loaded_uris.include?(path)
512
520
  raise CircularReferenceError, "can't load #{path} more than once" if @options[:io_exceptions]
513
- return false
521
+
522
+ false
514
523
  else
515
524
  @loaded_uris << path
516
- return true
525
+ true
517
526
  end
518
527
  end
519
528
 
@@ -541,7 +550,7 @@ module CssParser
541
550
  utf8_block = ignore_pattern(utf8_block, STRIP_HTML_COMMENTS_RX, options)
542
551
 
543
552
  # Strip lines containing just whitespace
544
- utf8_block.gsub!(/^\s+$/, "") unless options[:capture_offsets]
553
+ utf8_block.gsub!(/^\s+$/, '') unless options[:capture_offsets]
545
554
 
546
555
  utf8_block
547
556
  end
@@ -577,11 +586,8 @@ module CssParser
577
586
  if uri.scheme == 'file'
578
587
  # local file
579
588
  path = uri.path
580
- path.gsub!(/^\//, '') if Gem.win_platform?
581
- fh = open(path, 'rb')
582
- src = fh.read
583
- charset = fh.respond_to?(:charset) ? fh.charset : 'utf-8'
584
- fh.close
589
+ path.gsub!(%r{^/}, '') if Gem.win_platform?
590
+ src = File.read(path, mode: 'rb')
585
591
  else
586
592
  # remote file
587
593
  if uri.scheme == 'https'
@@ -593,27 +599,28 @@ module CssParser
593
599
  http = Net::HTTP.new(uri.host, uri.port)
594
600
  end
595
601
 
596
- res = http.get(uri.request_uri, {'User-Agent' => USER_AGENT, 'Accept-Encoding' => 'gzip'})
602
+ res = http.get(uri.request_uri, {'User-Agent' => @options[:user_agent], 'Accept-Encoding' => 'gzip'})
597
603
  src = res.body
598
604
  charset = res.respond_to?(:charset) ? res.encoding : 'utf-8'
599
605
 
600
606
  if res.code.to_i >= 400
601
607
  @redirect_count = nil
602
- raise RemoteFileError.new(uri.to_s) if @options[:io_exceptions]
608
+ raise RemoteFileError, uri.to_s if @options[:io_exceptions]
609
+
603
610
  return '', nil
604
611
  elsif res.code.to_i >= 300 and res.code.to_i < 400
605
- if res['Location'] != nil
612
+ unless res['Location'].nil?
606
613
  return read_remote_file Addressable::URI.parse(Addressable::URI.escape(res['Location']))
607
614
  end
608
615
  end
609
616
 
610
617
  case res['content-encoding']
611
- when 'gzip'
612
- io = Zlib::GzipReader.new(StringIO.new(res.body))
613
- src = io.read
614
- when 'deflate'
615
- io = Zlib::Inflate.new
616
- src = io.inflate(res.body)
618
+ when 'gzip'
619
+ io = Zlib::GzipReader.new(StringIO.new(res.body))
620
+ src = io.read
621
+ when 'deflate'
622
+ io = Zlib::Inflate.new
623
+ src = io.inflate(res.body)
617
624
  end
618
625
  end
619
626
 
@@ -627,15 +634,17 @@ module CssParser
627
634
  end
628
635
  rescue
629
636
  @redirect_count = nil
630
- raise RemoteFileError.new(uri.to_s)if @options[:io_exceptions]
637
+ raise RemoteFileError, uri.to_s if @options[:io_exceptions]
638
+
631
639
  return nil, nil
632
640
  end
633
641
 
634
642
  @redirect_count = nil
635
- return src, charset
643
+ [src, charset]
636
644
  end
637
645
 
638
646
  private
647
+
639
648
  # Save a folded declaration block to the internal cache.
640
649
  def save_folded_declaration(block_hash, folded_declaration) # :nodoc:
641
650
  @folded_declaration_cache[block_hash] = folded_declaration
@@ -643,7 +652,7 @@ module CssParser
643
652
 
644
653
  # Retrieve a folded declaration block from the internal cache.
645
654
  def get_folded_declaration(block_hash) # :nodoc:
646
- return @folded_declaration_cache[block_hash] ||= nil
655
+ @folded_declaration_cache[block_hash] ||= nil
647
656
  end
648
657
 
649
658
  def reset! # :nodoc:
@@ -657,14 +666,15 @@ module CssParser
657
666
  # passed hash
658
667
  def css_node_to_h(hash, key, val)
659
668
  hash[key.strip] = '' and return hash if val.nil?
669
+
660
670
  lines = val.split(';')
661
671
  nodes = {}
662
672
  lines.each do |line|
663
673
  parts = line.split(':', 2)
664
- if (parts[1] =~ /:/)
674
+ if parts[1] =~ /:/
665
675
  nodes[parts[0]] = css_node_to_h(hash, parts[0], parts[1])
666
676
  else
667
- nodes[parts[0].to_s.strip] =parts[1].to_s.strip
677
+ nodes[parts[0].to_s.strip] = parts[1].to_s.strip
668
678
  end
669
679
  end
670
680
  hash[key.strip] = nodes