premailer 1.12.0 → 1.29.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.
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  # Premailer processes HTML and CSS to improve e-mail deliverability.
2
3
  #
3
4
  # Premailer's main function is to render all CSS as inline <tt>style</tt>
@@ -15,7 +16,7 @@
15
16
  # fout.close
16
17
  #
17
18
  # # Write the plain-text output
18
- # fout = File.open("ouput.txt", "w")
19
+ # fout = File.open("output.txt", "w")
19
20
  # fout.puts premailer.to_plain_text
20
21
  # fout.close
21
22
  #
@@ -38,9 +39,9 @@ class Premailer
38
39
  CLIENT_SUPPORT_FILE = File.dirname(__FILE__) + '/../../misc/client_support.yaml'
39
40
 
40
41
  # Unmergable selectors regexp.
41
- RE_UNMERGABLE_SELECTORS = /(\:(visited|active|hover|focus|after|before|selection|target|first\-(line|letter))|^\@)/i
42
+ RE_UNMERGABLE_SELECTORS = /(:(visited|active|hover|focus|after|before|selection|target|first-(line|letter))|^@)/i
42
43
  # Reset selectors regexp.
43
- RE_RESET_SELECTORS = /^(\:\#outlook|body.*|\.ReadMsgBody|\.ExternalClass|img|\#backgroundTable)$/
44
+ RE_RESET_SELECTORS = /^(:\#outlook|body.*|\.ReadMsgBody|\.ExternalClass|img|\#backgroundTable)$/
44
45
 
45
46
  # list of HTMLEntities to fix
46
47
  # source: http://stackoverflow.com/questions/2812781/how-to-convert-webpage-apostrophe-8217-to-ascii-39-in-ruby-1-
@@ -56,23 +57,23 @@ class Premailer
56
57
  "&#8211;" => '-',
57
58
  "&#8212;" => '--',
58
59
  "&#8213;" => '--'
59
- }
60
+ }.freeze
60
61
 
61
62
  # list of CSS attributes that can be rendered as HTML attributes
62
63
  #
63
64
  # @todo too much repetition
64
65
  # @todo background=""
65
66
  RELATED_ATTRIBUTES = {
66
- 'h1' => {'text-align' => 'align'},
67
- 'h2' => {'text-align' => 'align'},
68
- 'h3' => {'text-align' => 'align'},
69
- 'h4' => {'text-align' => 'align'},
70
- 'h5' => {'text-align' => 'align'},
71
- 'h6' => {'text-align' => 'align'},
72
- 'p' => {'text-align' => 'align'},
73
- 'div' => {'text-align' => 'align'},
74
- 'blockquote' => {'text-align' => 'align'},
75
- 'body' => {'background-color' => 'bgcolor'},
67
+ 'h1' => { 'text-align' => 'align' },
68
+ 'h2' => { 'text-align' => 'align' },
69
+ 'h3' => { 'text-align' => 'align' },
70
+ 'h4' => { 'text-align' => 'align' },
71
+ 'h5' => { 'text-align' => 'align' },
72
+ 'h6' => { 'text-align' => 'align' },
73
+ 'p' => { 'text-align' => 'align' },
74
+ 'div' => { 'text-align' => 'align' },
75
+ 'blockquote' => { 'text-align' => 'align' },
76
+ 'body' => { 'background-color' => 'bgcolor' },
76
77
  'table' => {
77
78
  '-premailer-align' => 'align',
78
79
  'background-color' => 'bgcolor',
@@ -106,7 +107,7 @@ class Premailer
106
107
  '-premailer-width' => 'width',
107
108
  '-premailer-height' => 'height'
108
109
  }
109
- }
110
+ }.freeze
110
111
 
111
112
  # URI of the HTML file used
112
113
  attr_reader :html_file
@@ -141,7 +142,7 @@ class Premailer
141
142
  include Warnings
142
143
 
143
144
  # Waning level names
144
- WARN_LABEL = %w(NONE SAFE POOR RISKY)
145
+ WARN_LABEL = ['NONE', 'SAFE', 'POOR', 'RISKY'].freeze
145
146
 
146
147
  # Create a new Premailer object.
147
148
  #
@@ -168,7 +169,8 @@ class Premailer
168
169
  # @option options [Boolean] :preserve_reset Whether to preserve styles associated with the MailChimp reset code. Default is true.
169
170
  # @option options [Boolean] :with_html_string Whether the html param should be treated as a raw string. Default is false.
170
171
  # @option options [Boolean] :verbose Whether to print errors and warnings to <tt>$stderr</tt>. Default is false.
171
- # @option options [Boolean] :io_exceptions Throws exceptions on I/O errors.
172
+ # @option options [Boolean] :io_exceptions Throws exceptions on I/O errors. Default is false.
173
+ # @option options [Boolean] :rule_set_exceptions Throws exceptions on invalid values in CSS Parser rule sets. Default is true.
172
174
  # @option options [Boolean] :include_link_tags Whether to include css from <tt>link rel=stylesheet</tt> tags. Default is true.
173
175
  # @option options [Boolean] :include_style_tags Whether to include css from <tt>style</tt> tags. Default is true.
174
176
  # @option options [String] :input_encoding Manually specify the source documents encoding. This is a good idea. Default is ASCII-8BIT.
@@ -178,40 +180,42 @@ class Premailer
178
180
  # @option options [String] :output_encoding Output encoding option for Nokogiri adapter. Should be set to "US-ASCII" to output HTML entities instead of Unicode characters.
179
181
  # @option options [Boolean] :create_shorthands Combine several properties into a shorthand one, e.g. font: style weight size. Default is true.
180
182
  # @option options [Boolean] :html_fragment Handle HTML fragment without any HTML content wrappers. Default is false.
181
- # @option options [Boolean] :drop_unmergeable_css_rules Do not include unmergeable css rules in a <tt><style><tt> tag. Default is false.
183
+ # @option options [Boolean] :drop_unmergeable_css_rules Do not include unmergeable css rules in a <tt><style><tt> tag. Default is false.
182
184
  def initialize(html, options = {})
183
- @options = {:warn_level => Warnings::SAFE,
184
- :line_length => 65,
185
- :link_query_string => nil,
186
- :base_url => nil,
187
- :rgb_to_hex_attributes => true,
188
- :remove_classes => false,
189
- :remove_ids => false,
190
- :remove_comments => false,
191
- :remove_scripts => true,
192
- :reset_contenteditable => true,
193
- :css => [],
194
- :css_to_attributes => true,
195
- :preserve_style_attribute => false,
196
- :with_html_string => false,
197
- :css_string => nil,
198
- :preserve_styles => false,
199
- :preserve_reset => true,
200
- :verbose => false,
201
- :debug => false,
202
- :io_exceptions => false,
203
- :include_link_tags => true,
204
- :include_style_tags => true,
205
- :input_encoding => 'ASCII-8BIT',
206
- :output_encoding => nil,
207
- :replace_html_entities => false,
208
- :escape_url_attributes => true,
209
- :unescaped_ampersand => false,
210
- :create_shorthands => true,
211
- :html_fragment => false,
212
- :adapter => Adapter.use,
213
- :drop_unmergeable_css_rules => false
214
- }.merge(options)
185
+ @options = {
186
+ :warn_level => Warnings::SAFE,
187
+ :line_length => 65,
188
+ :link_query_string => nil,
189
+ :base_url => nil,
190
+ :rgb_to_hex_attributes => true,
191
+ :remove_classes => false,
192
+ :remove_ids => false,
193
+ :remove_comments => false,
194
+ :remove_scripts => true,
195
+ :reset_contenteditable => true,
196
+ :css => [],
197
+ :css_to_attributes => true,
198
+ :preserve_style_attribute => false,
199
+ :with_html_string => false,
200
+ :css_string => nil,
201
+ :preserve_styles => false,
202
+ :preserve_reset => true,
203
+ :verbose => false,
204
+ :debug => false,
205
+ :io_exceptions => false,
206
+ :rule_set_exceptions => true,
207
+ :include_link_tags => true,
208
+ :include_style_tags => true,
209
+ :input_encoding => 'ASCII-8BIT',
210
+ :output_encoding => nil,
211
+ :replace_html_entities => false,
212
+ :escape_url_attributes => true,
213
+ :unescaped_ampersand => false,
214
+ :create_shorthands => true,
215
+ :html_fragment => false,
216
+ :adapter => Adapter.use,
217
+ :drop_unmergeable_css_rules => false
218
+ }.merge(options)
215
219
 
216
220
  @html_file = html
217
221
  @is_local_file = @options[:with_html_string] || Premailer.local_data?(html)
@@ -226,19 +230,22 @@ class Premailer
226
230
 
227
231
  if @options[:base_url]
228
232
  @base_url = Addressable::URI.parse(@options.delete(:base_url))
229
- elsif not @is_local_file
233
+ elsif !@is_local_file
230
234
  @base_url = Addressable::URI.parse(@html_file)
231
235
  end
232
236
 
233
- @css_parser = CssParser::Parser.new({
234
- :absolute_paths => true,
235
- :import => true,
236
- :io_exceptions => @options[:io_exceptions]
237
- })
237
+ @css_parser = CssParser::Parser.new(
238
+ {
239
+ :absolute_paths => true,
240
+ :import => true,
241
+ :io_exceptions => @options[:io_exceptions],
242
+ :rule_set_exceptions => @options[:rule_set_exceptions]
243
+ }
244
+ )
238
245
 
239
246
  @adapter_class = Adapter.find @options[:adapter]
240
247
 
241
- self.extend(@adapter_class)
248
+ extend(@adapter_class)
242
249
 
243
250
  @doc = load_html(@html_file)
244
251
 
@@ -259,25 +266,26 @@ class Premailer
259
266
  @css_warnings
260
267
  end
261
268
 
262
- protected
269
+ protected
270
+
263
271
  def load_css_from_local_file!(path)
264
- css_block = ''
265
- path.gsub!(/\Afile:/, '')
272
+ css_block = +''
273
+ path.delete_prefix!('file:')
266
274
  begin
267
275
  File.open(path, "r") do |file|
268
- while line = file.gets
276
+ while (line = file.gets)
269
277
  css_block << line
270
278
  end
271
279
  end
272
280
 
273
281
  load_css_from_string(css_block)
274
- rescue => e
282
+ rescue StandardError => e
275
283
  raise e if @options[:io_exceptions]
276
284
  end
277
285
  end
278
286
 
279
287
  def load_css_from_string(css_string)
280
- @css_parser.add_block!(css_string, {:base_uri => @base_url, :base_dir => @base_dir, :only_media_types => [:screen, :handheld]})
288
+ @css_parser.add_block!(css_string, { :base_uri => @base_url, :base_dir => @base_dir, :only_media_types => [:screen, :handheld] })
281
289
  end
282
290
 
283
291
  # @private
@@ -293,23 +301,23 @@ protected
293
301
  end
294
302
  end
295
303
 
296
- # Load CSS included in <tt>style</tt> and <tt>link</tt> tags from an HTML document.
304
+ # Load CSS included in <tt>style</tt> and <tt>link</tt> tags from an HTML document.
297
305
  def load_css_from_html! # :nodoc:
298
306
  tags = @doc.search("link[@rel='stylesheet']:not([@data-premailer='ignore']), style:not([@data-premailer='ignore'])")
299
307
  if tags
300
308
  tags.each do |tag|
301
- if tag.to_s.strip =~ /^\<link/i && tag.attributes['href'] && media_type_ok?(tag.attributes['media']) && @options[:include_link_tags]
309
+ if tag.to_s.strip =~ /^<link/i && tag.attributes['href'] && media_type_ok?(tag.attributes['media']) && @options[:include_link_tags]
302
310
  # A user might want to <link /> to a local css file that is also mirrored on the site
303
311
  # but the local one is different (e.g. newer) than the live file, premailer will now choose the local file
304
312
 
305
- if tag.attributes['href'].to_s.include? @base_url.to_s and @html_file.kind_of?(String)
306
- if @options[:with_html_string]
307
- link_uri = tag.attributes['href'].to_s.sub(@base_url.to_s, '')
313
+ if tag.attributes['href'].to_s.include?(@base_url.to_s) && @html_file.is_a?(String)
314
+ link_uri = if @options[:with_html_string]
315
+ tag.attributes['href'].to_s.sub(@base_url.to_s, '')
308
316
  else
309
- link_uri = File.join(File.dirname(@html_file), tag.attributes['href'].to_s.sub!(@base_url.to_s, ''))
317
+ File.join(File.dirname(@html_file), tag.attributes['href'].to_s.sub!(@base_url.to_s, ''))
310
318
  end
311
319
  # if the file does not exist locally, try to grab the remote reference
312
- unless File.exists?(link_uri)
320
+ unless File.exist?(link_uri)
313
321
  link_uri = Premailer.resolve_link(tag.attributes['href'].to_s, @html_file)
314
322
  end
315
323
  else
@@ -317,14 +325,14 @@ protected
317
325
  end
318
326
 
319
327
  if Premailer.local_data?(link_uri)
320
- $stderr.puts "Loading css from local file: " + link_uri if @options[:verbose]
328
+ warn "Loading css from local file: " + link_uri if @options[:verbose]
321
329
  load_css_from_local_file!(link_uri)
322
330
  else
323
- $stderr.puts "Loading css from uri: " + link_uri if @options[:verbose]
324
- @css_parser.load_uri!(link_uri, {:only_media_types => [:screen, :handheld]})
331
+ warn "Loading css from uri: " + link_uri if @options[:verbose]
332
+ @css_parser.load_uri!(link_uri, { :only_media_types => [:screen, :handheld] })
325
333
  end
326
334
 
327
- elsif tag.to_s.strip =~ /^\<style/i && @options[:include_style_tags]
335
+ elsif tag.to_s.strip =~ /^<style/i && @options[:include_style_tags]
328
336
  @css_parser.add_block!(tag.inner_html, :base_uri => @base_url, :base_dir => @base_dir, :only_media_types => [:screen, :handheld])
329
337
  end
330
338
  end
@@ -332,10 +340,31 @@ protected
332
340
  end
333
341
  end
334
342
 
343
+ # here be deprecated methods
344
+ public
335
345
 
346
+ # Free native Nokogiri/libxml2 memory held by this instance.
347
+ #
348
+ # After calling +to_inline_css+ or +to_plain_text+, the Premailer instance
349
+ # retains live Nokogiri documents that pin native libxml2 memory. Ruby's GC
350
+ # cannot see this native memory pressure, so in high-throughput scenarios
351
+ # (e.g. email rendering in Sidekiq workers) native memory can accumulate
352
+ # unboundedly. Call +cleanup!+ when you are done with the instance to
353
+ # release that memory immediately.
354
+ #
355
+ # After calling this method the instance is no longer usable.
356
+ def cleanup!
357
+ @processed_doc&.unlink
358
+ @doc&.unlink
359
+ @doc = nil
360
+ @processed_doc = nil
361
+ @css_parser = nil
362
+ @unmergable_rules = nil
363
+ @html_file = nil
364
+ @css_warnings = nil
365
+ @css_files = nil
366
+ end
336
367
 
337
- # here be deprecated methods
338
- public
339
368
  # @private
340
369
  # @deprecated
341
370
  def local_uri?(uri) # :nodoc:
@@ -343,70 +372,70 @@ public
343
372
  Premailer.local_data?(uri)
344
373
  end
345
374
 
346
- # here be instance methods
375
+ # here be instance methods
347
376
 
348
377
  # @private
349
378
  def media_type_ok?(media_types)
350
379
  media_types = media_types.to_s
351
- return true if media_types.nil? or media_types.empty?
380
+ return true if media_types.nil? || media_types.empty?
352
381
  media_types.split(/[\s]+|,/).any? { |media_type| media_type.strip =~ /screen|handheld|all/i }
353
382
  end
354
383
 
355
- def append_query_string(doc, qs)
356
- return doc if qs.nil?
384
+ def append_query_string(doc, queries)
385
+ return doc if queries.nil?
357
386
 
358
- qs.to_s.gsub!(/^[\?]*/, '').strip!
359
- return doc if qs.empty?
387
+ queries = +queries
388
+ queries.to_s.gsub!(/^[\?]*/, '').strip!
389
+ return doc if queries.empty?
360
390
 
361
391
  begin
362
392
  current_host = @base_url.host
363
- rescue
393
+ rescue StandardError
364
394
  current_host = nil
365
395
  end
366
396
 
367
- $stderr.puts "Attempting to append_query_string: #{qs}" if @options[:verbose]
397
+ warn "Attempting to append_query_string: #{queries}" if @options[:verbose]
368
398
 
369
- doc.search('a').each do|el|
399
+ doc.search('a').each do |el|
370
400
  href = el.attributes['href'].to_s.strip
371
- next if href.nil? or href.empty?
401
+ next if href.nil? || href.empty?
372
402
 
373
- next if href[0,1] =~ /[\#\{\[\<\%]/ # don't bother with anchors or special-looking links
403
+ next if /[\#\{\[\<\%]/.match?(href[0, 1]) # don't bother with anchors or special-looking links
374
404
 
375
405
  begin
376
406
  href = Addressable::URI.parse(href)
377
407
 
378
- if current_host and href.host != nil and href.host != current_host
379
- $stderr.puts "Skipping append_query_string for: #{href.to_s} because host is no good" if @options[:verbose]
408
+ if current_host && !href.host.nil? && (href.host != current_host)
409
+ warn "Skipping append_query_string for: #{href} because host is no good" if @options[:verbose]
380
410
  next
381
411
  end
382
412
 
383
- if href.scheme and href.scheme != 'http' and href.scheme != 'https'
384
- puts "Skipping append_query_string for: #{href.to_s} because scheme is no good" if @options[:verbose]
413
+ if href.scheme && (href.scheme != 'http') && (href.scheme != 'https')
414
+ puts "Skipping append_query_string for: #{href} because scheme is no good" if @options[:verbose]
385
415
  next
386
416
  end
387
417
 
388
- if href.query and not href.query.empty?
418
+ if href.query && !href.query.empty?
389
419
  amp = @options[:unescaped_ampersand] ? '&' : '&amp;'
390
- href.query = href.query + amp + qs
420
+ href.query = href.query + amp + queries
391
421
  else
392
- href.query = qs
422
+ href.query = queries
393
423
  end
394
424
 
395
425
  el['href'] = href.to_s
396
426
  rescue Addressable::URI::InvalidURIError => e
397
- $stderr.puts "Skipping append_query_string for: #{href.to_s} (#{e.message})" if @options[:verbose]
427
+ warn "Skipping append_query_string for: #{href} (#{e.message})" if @options[:verbose]
398
428
  next
399
429
  end
400
-
401
430
  end
402
431
  doc
403
432
  end
404
433
 
405
434
  # Check for an XHTML doctype
406
- def is_xhtml?
435
+ def xhtml?
407
436
  intro = @doc.to_xhtml.strip.split("\n")[0..2].join(' ')
408
437
  is_xhtml = !!(intro =~ /w3c\/\/[\s]*dtd[\s]+xhtml/i)
409
- $stderr.puts "Is XHTML? #{is_xhtml.inspect}\nChecked:\n#{intro}" if @options[:debug]
438
+ warn "Is XHTML? #{is_xhtml.inspect}\nChecked:\n#{intro}" if @options[:debug]
410
439
  is_xhtml
411
440
  end
412
441
 
@@ -419,9 +448,8 @@ public
419
448
  #
420
449
  # Returns a document.
421
450
  def convert_inline_links(doc, base_uri) # :nodoc:
422
- base_uri = Addressable::URI.parse(base_uri) unless base_uri.kind_of?(Addressable::URI)
451
+ base_uri = Addressable::URI.parse(base_uri) unless base_uri.is_a?(Addressable::URI)
423
452
 
424
- append_qs = @options[:link_query_string] || ''
425
453
  escape_attrs = @options[:escape_url_attributes]
426
454
 
427
455
  ['href', 'src', 'background'].each do |attribute|
@@ -432,30 +460,30 @@ public
432
460
  tags.each do |tag|
433
461
  # skip links that look like they have merge tags
434
462
  # and mailto, ftp, etc...
435
- if tag.attributes[attribute].to_s =~ /^([\%\<\{\#\[]|data:|tel:|file:|sms:|callto:|facetime:|mailto:|ftp:|gopher:|cid:)/i
463
+ if /^([\%\<\{\#\[]|data:|tel:|file:|sms:|callto:|facetime:|mailto:|ftp:|gopher:|cid:)/i.match?(tag.attributes[attribute].to_s)
436
464
  next
437
465
  end
438
466
 
439
- if tag.attributes[attribute].to_s =~ /^http/i
467
+ if /^http/i.match?(tag.attributes[attribute].to_s)
440
468
  begin
441
469
  merged = Addressable::URI.parse(tag.attributes[attribute])
442
- rescue; next; end
470
+ rescue StandardError; next; end
443
471
  else
444
472
  begin
445
473
  merged = Premailer.resolve_link(tag.attributes[attribute].to_s, base_uri)
446
- rescue
474
+ rescue StandardError
447
475
  begin
448
476
  next unless escape_attrs
449
477
  merged = Premailer.resolve_link(Addressable::URI.escape(tag.attributes[attribute].to_s), base_uri)
450
- rescue; end
478
+ rescue StandardError; end
451
479
  end
452
480
  end
453
481
 
454
482
  # make sure 'merged' is a URI
455
- merged = Addressable::URI.parse(merged.to_s) unless merged.kind_of?(Addressable::URI)
483
+ merged = Addressable::URI.parse(merged.to_s) unless merged.is_a?(Addressable::URI)
456
484
  tag[attribute] = merged.to_s
457
- end # end of each tag
458
- end # end of each attrs
485
+ end
486
+ end
459
487
 
460
488
  doc.search("*[@style]").each do |el|
461
489
  el['style'] = CssParser.convert_uris(el.attributes['style'].to_s, base_uri)
@@ -464,21 +492,21 @@ public
464
492
  end
465
493
 
466
494
  # @private
467
- def self.is_media_query?(media_types)
468
- media_types && media_types.any?{|mt| mt.to_s.count('()') >= 2 }
495
+ def self.media_query?(media_types)
496
+ media_types&.any? { |mt| mt.to_s.count('()') >= 2 }
469
497
  end
470
498
 
471
499
  # @private
472
500
  def self.resolve_link(path, base_path) # :nodoc:
501
+ path = +path
473
502
  path.strip!
474
- resolved = nil
475
- if path =~ /\A(?:(https?|ftp|file):)\/\//i
503
+ if /\A(?:(https?|ftp|file):)\/\//i.match?(path)
476
504
  resolved = path
477
505
  Premailer.canonicalize(resolved)
478
- elsif base_path.kind_of?(Addressable::URI)
506
+ elsif base_path.is_a?(Addressable::URI)
479
507
  resolved = base_path.join(path)
480
508
  Premailer.canonicalize(resolved)
481
- elsif base_path.kind_of?(String) and base_path =~ /\A(?:(?:https?|ftp|file):)\/\//i
509
+ elsif base_path.is_a?(String) && base_path =~ (/\A(?:(?:https?|ftp|file):)\/\//i)
482
510
  resolved = Addressable::URI.parse(base_path)
483
511
  resolved = resolved.join(path)
484
512
  Premailer.canonicalize(resolved)
@@ -491,33 +519,28 @@ public
491
519
  #
492
520
  # IO objects return true, as do strings that look like URLs.
493
521
  def self.local_data?(data)
494
- return false if data.kind_of?(String) && data =~ /\A(?:(https?|ftp):)\/\//i
522
+ return false if data.is_a?(String) && data =~ /\A(?:(https?|ftp):)\/\//i
495
523
  true
496
524
  end
497
525
 
498
526
  # from http://www.ruby-forum.com/topic/140101
499
527
  def self.canonicalize(uri) # :nodoc:
500
- u = uri.kind_of?(Addressable::URI) ? uri : Addressable::URI.parse(uri.to_s)
528
+ u = uri.is_a?(Addressable::URI) ? uri : Addressable::URI.parse(uri.to_s)
501
529
  u.normalize!
502
- newpath = u.path
503
- while newpath.gsub!(%r{([^/]+)/\.\./?}) { |match|
504
- $1 == '..' ? match : ''
505
- } do end
506
- newpath = newpath.gsub(%r{/\./}, '/').sub(%r{/\.\z}, '/')
507
- u.path = newpath
508
- u.to_s
509
- end
530
+ u.to_s
531
+ end
510
532
 
511
533
  # Check <tt>CLIENT_SUPPORT_FILE</tt> for any CSS warnings
512
534
  def check_client_support # :nodoc:
513
- @client_support ||= YAML::load(File.open(CLIENT_SUPPORT_FILE))
535
+ kwargs = Psych::VERSION >= '4' ? { aliases: true } : {}
536
+ @client_support ||= Psych.load(File.open(CLIENT_SUPPORT_FILE), **kwargs)
514
537
 
515
538
  warnings = []
516
539
  properties = []
517
540
 
518
541
  # Get a list off CSS properties
519
542
  @processed_doc.search("*[@style]").each do |el|
520
- style_url = el.attributes['style'].to_s.gsub(/([\w\-]+)[\s]*\:/i) do |s|
543
+ el.attributes['style'].to_s.gsub(/([\w\-]+)[\s]*:/i) do |_s|
521
544
  properties.push($1)
522
545
  end
523
546
  end
@@ -526,30 +549,42 @@ public
526
549
 
527
550
  property_support = @client_support['css_properties']
528
551
  properties.each do |prop|
529
- if property_support.include?(prop) and
530
- property_support[prop].include?('support') and
531
- property_support[prop]['support'] >= @options[:warn_level]
532
- warnings.push({:message => "#{prop} CSS property",
552
+ if property_support.include?(prop) &&
553
+ property_support[prop].include?('support') &&
554
+ (property_support[prop]['support'] >= @options[:warn_level])
555
+ warnings.push(
556
+ {
557
+ :message => "#{prop} CSS property",
533
558
  :level => WARN_LABEL[property_support[prop]['support']],
534
- :clients => property_support[prop]['unsupported_in'].join(', ')})
559
+ :clients => property_support[prop]['unsupported_in'].join(', ')
560
+ }
561
+ )
535
562
  end
536
563
  end
537
564
 
538
565
  @client_support['attributes'].each do |attribute, data|
539
566
  next unless data['support'] >= @options[:warn_level]
540
567
  if @doc.search("*[@#{attribute}]").length > 0
541
- warnings.push({:message => "#{attribute} HTML attribute",
568
+ warnings.push(
569
+ {
570
+ :message => "#{attribute} HTML attribute",
542
571
  :level => WARN_LABEL[data['support']],
543
- :clients => data['unsupported_in'].join(', ')})
572
+ :clients => data['unsupported_in'].join(', ')
573
+ }
574
+ )
544
575
  end
545
576
  end
546
577
 
547
578
  @client_support['elements'].each do |element, data|
548
579
  next unless data['support'] >= @options[:warn_level]
549
580
  if @doc.search(element).length > 0
550
- warnings.push({:message => "#{element} HTML element",
581
+ warnings.push(
582
+ {
583
+ :message => "#{element} HTML element",
551
584
  :level => WARN_LABEL[data['support']],
552
- :clients => data['unsupported_in'].join(', ')})
585
+ :clients => data['unsupported_in'].join(', ')
586
+ }
587
+ )
553
588
  end
554
589
  end
555
590
 
@@ -1,4 +1,5 @@
1
+ # frozen_string_literal: true
1
2
  class Premailer
2
3
  # Premailer version.
3
- VERSION = '1.12.0'.freeze
4
+ VERSION = '1.29.0'
4
5
  end
data/lib/premailer.rb CHANGED
@@ -1,6 +1,7 @@
1
+ # frozen_string_literal: true
1
2
  require 'yaml'
2
3
  require 'open-uri'
3
- require 'digest/md5'
4
+ require 'digest/sha2'
4
5
  require 'cgi'
5
6
  require 'addressable/uri'
6
7
  require 'css_parser'
@@ -9,3 +10,4 @@ require 'premailer/adapter'
9
10
  require 'premailer/adapter/rgb_to_hex'
10
11
  require 'premailer/html_to_plain_text'
11
12
  require 'premailer/premailer'
13
+ require 'premailer/cached_rule_set'