premailer 1.16.0 → 1.27.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c5569e920c965e583c48c9f8501b77b951c73d07c9be2f922ab4924dc1166871
4
- data.tar.gz: a8a665fe6ec7ba543b53ca24869e3c73bbf793cb003e4d27e4f854523309b605
3
+ metadata.gz: 31108244f6afc74508eef1337a9b99adc7f2643b5412e2aacf808ecb8c520101
4
+ data.tar.gz: b2f10188d4b7143050eb86e04776c38fc220ebb834da7871e838fe47db5fe9c5
5
5
  SHA512:
6
- metadata.gz: 3dc465f0f358d41f34ceef3c35fceda64f18d0b3e6ef27497dd649f4ce775c37a52d0f7ff12e5702e67ba6dc59760cedc5c706e8194a0b3063c9215644299f76
7
- data.tar.gz: 78aa6ffd3c33aabc6eab536d09c4f00bb5a775e32db01b3445aa96530af1a1d950760de0cc99609008dd0d1af01d6976eacd298fb4acb705005070faa7c4c747
6
+ metadata.gz: 4cd3655f0aa411da6d874dd50e2e1ab1c6f1cae4f9882cfa84a6ec2022174f5d9670d439527bb27618774f7f731f7ac24b35420c58926db159b4f81e85cb660d
7
+ data.tar.gz: aa583335ee3a7b0de99320aff3c162d233825c99fbfe287ae13f12790c4b54728b7b0460861f928efe9ddae07416aafb229058f698267214e8b740ff94ea20a8
data/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  For the best HTML e-mail delivery results, CSS should be inline. This is a
6
6
  huge pain and a simple newsletter becomes un-managable very quickly. This
7
- script is my solution.
7
+ gem is a solution.
8
8
 
9
9
  * CSS styles are converted to inline style attributes
10
10
  - Checks `style` and `link[rel=stylesheet]` tags and preserves existing inline attributes
@@ -12,35 +12,26 @@ script is my solution.
12
12
  - Checks links in `href`, `src` and CSS `url('')`
13
13
  * CSS properties are checked against e-mail client capabilities
14
14
  - Based on the Email Standards Project's guides
15
- * A [plain text version](https://premailer.github.io/premailer/HtmlToPlainText.html) is created (optional)
15
+ * A [plain text version](#plain-text-version) is created (optional)
16
16
 
17
17
  ## Installation
18
18
 
19
- Install the Premailer gem from RubyGems.
20
-
21
19
  ```bash
22
20
  gem install premailer
23
21
  ```
24
22
 
25
- or add it to your `Gemfile` and run `bundle`.
26
-
27
23
  ## Example
28
24
 
29
25
  ```ruby
30
26
  require 'premailer'
31
27
 
32
- premailer = Premailer.new('http://example.com/myfile.html', :warn_level => Premailer::Warnings::SAFE)
28
+ premailer = Premailer.new('http://example.com/myfile.html', warn_level: Premailer::Warnings::SAFE)
33
29
 
34
- # Write the plain-text output
35
- # This must come before to_inline_css (https://github.com/premailer/premailer/issues/201)
36
- File.open("output.txt", "w") do |fout|
37
- fout.puts premailer.to_plain_text
38
- end
30
+ # Write the plain-text output (must come before to_inline_css)
31
+ File.write "output.txt", premailer.to_plain_text
39
32
 
40
33
  # Write the HTML output
41
- File.open("output.html", "w") do |fout|
42
- fout.puts premailer.to_inline_css
43
- end
34
+ File.write "output.html", premailer.to_inline_css
44
35
 
45
36
  # Output any CSS warnings
46
37
  premailer.warnings.each do |w|
@@ -50,17 +41,13 @@ end
50
41
 
51
42
  ## Adapters
52
43
 
53
- Premailer's default adapter is nokogiri if both nokogiri and nokogumbo are included in the Gemfile list. However, if you want to use a different adapter, you can choose to.
54
-
55
- There are three adapters in total (as of premailer 1.10.0)
56
-
57
44
  1. nokogiri (default)
58
- 2. nokogiri_fast
45
+ 2. nokogiri_fast (20x speed, more memory)
59
46
  3. nokogumbo
60
47
 
61
- hpricot adapter removed due to its EOL, please use `~>1.9.0` version if You still need it..
48
+ (hpricot adapter removed, use `~>1.9.0` version if you need it)
62
49
 
63
- `NokogiriFast` adapter improves the Algorithmic complexity of the running time by 20x with a slight compensation on memory. To switch to any of these adapters, add the following line. For example, if you want to include the `NokogiriFast` adapter,
50
+ Picking an adapter:
64
51
 
65
52
  ```ruby
66
53
  Premailer::Adapter.use = :nokogiri_fast
@@ -97,22 +84,56 @@ will result in
97
84
  <table cellspacing='5' width='500'>
98
85
  ```
99
86
 
100
- ## Configuration options
87
+ ## Plain text version
88
+
89
+ Premailer can generate a plain text version of your HTML. Links and images will be inlined.
101
90
 
102
- The behavior of Premailer can be configured by passing options in the initializer.
91
+ For example
103
92
 
104
- For example, the following will accept HTML from a string and will exclude unmergeable css from being added to the `<head>` of the output document.
93
+ ```html
94
+ <a href="https://example.com" >
95
+ <img src="https://github.com/premailer.png" alt="Premailer Logo" />
96
+ </a>
97
+ ```
105
98
 
99
+ will become
100
+
101
+ ```text
102
+ Premailer Logo ( https://example.com )
103
+ ```
104
+
105
+ To ignore/omit a section of HTML content from the plain text version, wrap it with the following comments.
106
+
107
+ ```html
108
+ <!-- start text/html -->
109
+ <p>This will be omitted from the plain text version.</p>
110
+ <p>
111
+ This is extremely helpful for <strong>removing email headers and footers</strong>
112
+ that aren't needed in the text version.
113
+ </p>
114
+ <!-- end text/html -->
115
+ ```
116
+
117
+ ## Configuration options
118
+
119
+ For example:
106
120
  ```ruby
107
- premailer = Premailer.new(html_string, with_html_string: true, drop_unmergeable_css_rules: true)
121
+ Premailer.new(
122
+ html, # html as string
123
+ with_html_string: true,
124
+ drop_unmergeable_css_rules: true
125
+ )
108
126
  ```
109
127
 
110
- [See here for a full list of the available options](https://premailer.github.io/premailer/Premailer.html#initialize-instance_method).
128
+ [available options](https://premailer.github.io/premailer/Premailer.html#initialize-instance_method)
111
129
 
112
130
 
113
131
  ## Contributions
114
132
 
115
- Contributions are most welcome. Premailer was rotting away in a private SVN repository for too long and could use some TLC. Fork and patch to your heart's content. Please don't increment the version numbers, though.
133
+ Contributions are most welcome.
134
+ Premailer was rotting away in a private SVN repository for too long and could use some TLC.
135
+ Fork and patch to your heart's content.
136
+ Please don't increment the version numbers.
116
137
 
117
138
  A few areas that are particularly in need of love:
118
139
 
@@ -129,4 +150,3 @@ and to [Campaign Monitor](https://www.campaignmonitor.com/) for supporting the w
129
150
  The source code can be found on [GitHub](https://github.com/premailer/premailer).
130
151
 
131
152
  Copyright by Alex Dunae (dunae.ca, e-mail 'code' at the same domain), 2007-2017. See [LICENSE.md](https://github.com/premailer/premailer/blob/master/LICENSE.md) for license details.
132
-
data/bin/premailer CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
4
  # This binary used in rubygems environment only as part of installed gem
4
5
 
5
6
  require 'premailer/executor'
6
-
@@ -1,9 +1,11 @@
1
+ # frozen_string_literal: true
1
2
  require 'nokogiri'
2
3
 
3
4
  class Premailer
4
5
  module Adapter
5
6
  # Nokogiri adapter
6
7
  module Nokogiri
8
+ WIDTH_AND_HIGHT = ['width', 'height'].freeze
7
9
 
8
10
  include AdapterHelper::RgbToHex
9
11
  # Merge CSS into the HTML document.
@@ -27,13 +29,13 @@ class Premailer
27
29
  selector.gsub!(/([\s]|^)([\w]+)/) { |m| $1.to_s + $2.to_s.downcase }
28
30
 
29
31
  if Premailer.is_media_query?(media_types) || selector =~ Premailer::RE_UNMERGABLE_SELECTORS
30
- @unmergable_rules.add_rule_set!(CssParser::RuleSet.new(selector, declaration), media_types) unless @options[:preserve_styles]
32
+ @unmergable_rules.add_rule_set!(CssParser::RuleSet.new(selectors: selector, block: declaration), media_types) unless @options[:preserve_styles]
31
33
  else
32
34
  begin
33
- if selector =~ Premailer::RE_RESET_SELECTORS
35
+ if Premailer::RE_RESET_SELECTORS.match?(selector)
34
36
  # this is in place to preserve the MailChimp CSS reset: http://github.com/mailchimp/Email-Blueprints/
35
37
  # however, this doesn't mean for testing pur
36
- @unmergable_rules.add_rule_set!(CssParser::RuleSet.new(selector, declaration)) unless !@options[:preserve_reset]
38
+ @unmergable_rules.add_rule_set!(CssParser::RuleSet.new(selectors: selector, block: declaration)) unless !@options[:preserve_reset]
37
39
  end
38
40
 
39
41
  # Change single ID CSS selectors into xpath so that we can match more
@@ -61,15 +63,21 @@ class Premailer
61
63
  doc.search("*[@style]").each do |el|
62
64
  style = el.attributes['style'].to_s
63
65
 
64
- declarations = []
65
- style.scan(/\[SPEC\=([\d]+)\[(.[^\]\]]*)\]\]/).each do |declaration|
66
- rs = CssParser::RuleSet.new(nil, declaration[1].to_s, declaration[0].to_i)
67
- declarations << rs
66
+ declarations = style.scan(/\[SPEC\=([\d]+)\[(.[^\]\]]*)\]\]/m).filter_map do |declaration|
67
+ rs = Premailer::CachedRuleSet.new(block: declaration[1].to_s, specificity: declaration[0].to_i)
68
+ rs.expand_shorthand!
69
+ rs
70
+ rescue ArgumentError => e
71
+ raise e if @options[:rule_set_exceptions]
68
72
  end
69
73
 
70
74
  # Perform style folding
71
75
  merged = CssParser.merge(declarations)
72
- merged.expand_shorthand!
76
+ begin
77
+ merged.expand_shorthand!
78
+ rescue ArgumentError => e
79
+ raise e if @options[:rule_set_exceptions]
80
+ end
73
81
 
74
82
  # Duplicate CSS attributes as HTML attributes
75
83
  if Premailer::RELATED_ATTRIBUTES.has_key?(el.name) && @options[:css_to_attributes]
@@ -84,7 +92,7 @@ class Premailer
84
92
  new_val.gsub!(/;$|\s*!important/, '').strip!
85
93
 
86
94
  # For width and height tags, remove px units
87
- new_val.gsub!(/(\d+)px/, '\1') if %w[width height].include?(html_attr)
95
+ new_val.gsub!(/(\d+)px/, '\1') if WIDTH_AND_HIGHT.include?(html_attr)
88
96
 
89
97
  # For color-related tags, convert RGB to hex if specified by options
90
98
  new_val = ensure_hex(new_val) if css_attr.end_with?('color') && @options[:rgb_to_hex_attributes]
@@ -93,7 +101,7 @@ class Premailer
93
101
  end
94
102
 
95
103
  unless @options[:preserve_style_attribute]
96
- merged.instance_variable_get("@declarations").tap do |declarations|
104
+ merged.instance_variable_get(:@declarations).tap do |declarations|
97
105
  declarations.delete(css_attr)
98
106
  end
99
107
  end
@@ -229,18 +237,12 @@ class Premailer
229
237
 
230
238
  # Handle HTML entities
231
239
  if @options[:replace_html_entities] == true and thing.is_a?(String)
240
+ thing = +thing
232
241
  HTML_ENTITIES.map do |entity, replacement|
233
242
  thing.gsub! entity, replacement
234
243
  end
235
244
  end
236
- # Default encoding is ASCII-8BIT (binary) per http://groups.google.com/group/nokogiri-talk/msg/0b81ef0dc180dc74
237
- # However, we really don't want to hardcode this. ASCII-8BIT should be the default, but not the only option.
238
- encoding = if thing.is_a?(String) and RUBY_VERSION =~ /1.9/
239
- thing = thing.force_encoding(@options[:input_encoding]).encode!
240
- @options[:input_encoding]
241
- else
242
- @options[:input_encoding] || (RUBY_PLATFORM == 'java' ? nil : 'BINARY')
243
- end
245
+ encoding = @options[:input_encoding] || (RUBY_PLATFORM == 'java' ? nil : 'BINARY')
244
246
  doc = if @options[:html_fragment]
245
247
  ::Nokogiri::HTML.fragment(thing, encoding)
246
248
  else
@@ -250,7 +252,7 @@ class Premailer
250
252
  # Fix for removing any CDATA tags from both style and script tags inserted per
251
253
  # https://github.com/sparklemotion/nokogiri/issues/311 and
252
254
  # https://github.com/premailer/premailer/issues/199
253
- %w(style script).each do |tag|
255
+ ['style', 'script'].each do |tag|
254
256
  doc.search(tag).children.each do |child|
255
257
  child.swap(child.text()) if child.cdata?
256
258
  end
@@ -1,9 +1,11 @@
1
+ # frozen_string_literal: true
1
2
  require 'nokogiri'
2
3
 
3
4
  class Premailer
4
5
  module Adapter
5
6
  # NokogiriFast adapter
6
7
  module NokogiriFast
8
+ WIDTH_AND_HEIGHT = ['width', 'height'].freeze
7
9
 
8
10
  include AdapterHelper::RgbToHex
9
11
  # Merge CSS into the HTML document.
@@ -33,13 +35,13 @@ class Premailer
33
35
  selector.gsub!(/([\s]|^)([\w]+)/) { |m| $1.to_s + $2.to_s.downcase }
34
36
 
35
37
  if Premailer.is_media_query?(media_types) || selector =~ Premailer::RE_UNMERGABLE_SELECTORS
36
- @unmergable_rules.add_rule_set!(CssParser::RuleSet.new(selector, declaration), media_types) unless @options[:preserve_styles]
38
+ @unmergable_rules.add_rule_set!(CssParser::RuleSet.new(selectors: selector, block: declaration), media_types) unless @options[:preserve_styles]
37
39
  else
38
40
  begin
39
- if selector =~ Premailer::RE_RESET_SELECTORS
41
+ if Premailer::RE_RESET_SELECTORS.match?(selector)
40
42
  # this is in place to preserve the MailChimp CSS reset: http://github.com/mailchimp/Email-Blueprints/
41
43
  # however, this doesn't mean for testing pur
42
- @unmergable_rules.add_rule_set!(CssParser::RuleSet.new(selector, declaration)) unless !@options[:preserve_reset]
44
+ @unmergable_rules.add_rule_set!(CssParser::RuleSet.new(selectors: selector, block: declaration)) unless !@options[:preserve_reset]
43
45
  end
44
46
 
45
47
  # Try the new index based technique. If not supported, fall back to the old brute force one.
@@ -66,14 +68,22 @@ class Premailer
66
68
  style = el.attributes['style'].to_s
67
69
 
68
70
  declarations = []
69
- style.scan(/\[SPEC\=([\d]+)\[(.[^\]\]]*)\]\]/).each do |declaration|
70
- rs = CssParser::RuleSet.new(nil, declaration[1].to_s, declaration[0].to_i)
71
- declarations << rs
71
+ style.scan(/\[SPEC\=([\d]+)\[(.[^\]\]]*)\]\]/m).each do |declaration|
72
+ begin
73
+ rs = CssParser::RuleSet.new(block: declaration[1].to_s, specificity: declaration[0].to_i)
74
+ declarations << rs
75
+ rescue ArgumentError => e
76
+ raise e if @options[:rule_set_exceptions]
77
+ end
72
78
  end
73
79
 
74
80
  # Perform style folding
75
81
  merged = CssParser.merge(declarations)
76
- merged.expand_shorthand!
82
+ begin
83
+ merged.expand_shorthand!
84
+ rescue ArgumentError => e
85
+ raise e if @options[:rule_set_exceptions]
86
+ end
77
87
 
78
88
  # Duplicate CSS attributes as HTML attributes
79
89
  if Premailer::RELATED_ATTRIBUTES.has_key?(el.name) && @options[:css_to_attributes]
@@ -88,7 +98,7 @@ class Premailer
88
98
  new_val.gsub!(/;$|\s*!important/, '').strip!
89
99
 
90
100
  # For width and height tags, remove px units
91
- new_val.gsub!(/(\d+)px/, '\1') if %w[width height].include?(html_attr)
101
+ new_val.gsub!(/(\d+)px/, '\1') if WIDTH_AND_HEIGHT.include?(html_attr)
92
102
 
93
103
  # For color-related tags, convert RGB to hex if specified by options
94
104
  new_val = ensure_hex(new_val) if css_attr.end_with?('color') && @options[:rgb_to_hex_attributes]
@@ -97,7 +107,7 @@ class Premailer
97
107
  end
98
108
 
99
109
  unless @options[:preserve_style_attribute]
100
- merged.instance_variable_get("@declarations").tap do |declarations|
110
+ merged.instance_variable_get(:@declarations).tap do |declarations|
101
111
  declarations.delete(css_attr)
102
112
  end
103
113
  end
@@ -237,14 +247,7 @@ class Premailer
237
247
  thing.gsub! entity, replacement
238
248
  end
239
249
  end
240
- # Default encoding is ASCII-8BIT (binary) per http://groups.google.com/group/nokogiri-talk/msg/0b81ef0dc180dc74
241
- # However, we really don't want to hardcode this. ASCII-8BIT should be the default, but not the only option.
242
- encoding = if thing.is_a?(String) and RUBY_VERSION =~ /1.9/
243
- thing = thing.force_encoding(@options[:input_encoding]).encode!
244
- @options[:input_encoding]
245
- else
246
- @options[:input_encoding] || (RUBY_PLATFORM == 'java' ? nil : 'BINARY')
247
- end
250
+ encoding = @options[:input_encoding] || (RUBY_PLATFORM == 'java' ? nil : 'BINARY')
248
251
  doc = if @options[:html_fragment]
249
252
  ::Nokogiri::HTML.fragment(thing, encoding)
250
253
  else
@@ -254,7 +257,7 @@ class Premailer
254
257
  # Fix for removing any CDATA tags from both style and script tags inserted per
255
258
  # https://github.com/sparklemotion/nokogiri/issues/311 and
256
259
  # https://github.com/premailer/premailer/issues/199
257
- %w(style script).each do |tag|
260
+ ['style', 'script'].each do |tag|
258
261
  doc.search(tag).children.each do |child|
259
262
  child.swap(child.text()) if child.cdata?
260
263
  end
@@ -313,7 +316,7 @@ class Premailer
313
316
  index.default = Set.new
314
317
  descendants.default = Set.new
315
318
 
316
- return index, Set.new(all_nodes), descendants
319
+ [index, Set.new(all_nodes), descendants]
317
320
  end
318
321
 
319
322
  # @param doc The top level document
@@ -353,7 +356,7 @@ class Premailer
353
356
  # It will return nil when such a selector is passed, so you can take
354
357
  # action on the falsity of the return value.
355
358
  def match_selector(index, all_nodes, descendants, selector)
356
- if /[^-a-zA-Z0-9_\s.#]/.match(selector) then
359
+ if /[^-a-zA-Z0-9_\s.#]/.match?(selector) then
357
360
  return nil
358
361
  end
359
362
 
@@ -1,7 +1,9 @@
1
+ # frozen_string_literal: true
1
2
  class Premailer
2
3
  module Adapter
3
4
  # Nokogiri adapter
4
5
  module Nokogumbo
6
+ WIDTH_AND_HEIGHT = ['width', 'height'].freeze
5
7
 
6
8
  include AdapterHelper::RgbToHex
7
9
  # Merge CSS into the HTML document.
@@ -25,13 +27,13 @@ class Premailer
25
27
  selector.gsub!(/([\s]|^)([\w]+)/) { |m| $1.to_s + $2.to_s.downcase }
26
28
 
27
29
  if Premailer.is_media_query?(media_types) || selector =~ Premailer::RE_UNMERGABLE_SELECTORS
28
- @unmergable_rules.add_rule_set!(CssParser::RuleSet.new(selector, declaration), media_types) unless @options[:preserve_styles]
30
+ @unmergable_rules.add_rule_set!(CssParser::RuleSet.new(selectors: selector, block: declaration), media_types) unless @options[:preserve_styles]
29
31
  else
30
32
  begin
31
- if selector =~ Premailer::RE_RESET_SELECTORS
33
+ if Premailer::RE_RESET_SELECTORS.match?(selector)
32
34
  # this is in place to preserve the MailChimp CSS reset: http://github.com/mailchimp/Email-Blueprints/
33
35
  # however, this doesn't mean for testing pur
34
- @unmergable_rules.add_rule_set!(CssParser::RuleSet.new(selector, declaration)) unless !@options[:preserve_reset]
36
+ @unmergable_rules.add_rule_set!(CssParser::RuleSet.new(selectors: selector, block: declaration)) unless !@options[:preserve_reset]
35
37
  end
36
38
 
37
39
  # Change single ID CSS selectors into xpath so that we can match more
@@ -60,14 +62,22 @@ class Premailer
60
62
  style = el.attributes['style'].to_s
61
63
 
62
64
  declarations = []
63
- style.scan(/\[SPEC\=([\d]+)\[(.[^\]\]]*)\]\]/).each do |declaration|
64
- rs = CssParser::RuleSet.new(nil, declaration[1].to_s, declaration[0].to_i)
65
- declarations << rs
65
+ style.scan(/\[SPEC\=([\d]+)\[(.[^\]\]]*)\]\]/m).each do |declaration|
66
+ begin
67
+ rs = CssParser::RuleSet.new(block: declaration[1].to_s, specificity: declaration[0].to_i)
68
+ declarations << rs
69
+ rescue ArgumentError => e
70
+ raise e if @options[:rule_set_exceptions]
71
+ end
66
72
  end
67
73
 
68
74
  # Perform style folding
69
75
  merged = CssParser.merge(declarations)
70
- merged.expand_shorthand!
76
+ begin
77
+ merged.expand_shorthand!
78
+ rescue ArgumentError => e
79
+ raise e if @options[:rule_set_exceptions]
80
+ end
71
81
 
72
82
  # Duplicate CSS attributes as HTML attributes
73
83
  if Premailer::RELATED_ATTRIBUTES.has_key?(el.name) && @options[:css_to_attributes]
@@ -82,7 +92,7 @@ class Premailer
82
92
  new_val.gsub!(/;$|\s*!important/, '').strip!
83
93
 
84
94
  # For width and height tags, remove px units
85
- new_val.gsub!(/(\d+)px/, '\1') if %w[width height].include?(html_attr)
95
+ new_val.gsub!(/(\d+)px/, '\1') if WIDTH_AND_HEIGHT.include?(html_attr)
86
96
 
87
97
  # For color-related tags, convert RGB to hex if specified by options
88
98
  new_val = ensure_hex(new_val) if css_attr.end_with?('color') && @options[:rgb_to_hex_attributes]
@@ -91,7 +101,7 @@ class Premailer
91
101
  end
92
102
 
93
103
  unless @options[:preserve_style_attribute]
94
- merged.instance_variable_get("@declarations").tap do |declarations|
104
+ merged.instance_variable_get(:@declarations).tap do |declarations|
95
105
  declarations.delete(css_attr)
96
106
  end
97
107
  end
@@ -231,11 +241,6 @@ class Premailer
231
241
  thing.gsub! entity, replacement
232
242
  end
233
243
  end
234
- # Default encoding is ASCII-8BIT (binary) per http://groups.google.com/group/nokogiri-talk/msg/0b81ef0dc180dc74
235
- # However, we really don't want to hardcode this. ASCII-8BIT should be the default, but not the only option.
236
- if thing.is_a?(String) and RUBY_VERSION =~ /1.9/
237
- thing = thing.force_encoding(@options[:input_encoding]).encode!
238
- end
239
244
  doc = if @options[:html_fragment]
240
245
  ::Nokogiri::HTML5.fragment(thing)
241
246
  else
@@ -245,7 +250,7 @@ class Premailer
245
250
  # Fix for removing any CDATA tags from both style and script tags inserted per
246
251
  # https://github.com/sparklemotion/nokogiri/issues/311 and
247
252
  # https://github.com/premailer/premailer/issues/199
248
- %w(style script).each do |tag|
253
+ ['style', 'script'].each do |tag|
249
254
  doc.search(tag).children.each do |child|
250
255
  child.swap(child.text()) if child.cdata?
251
256
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  # RGB helper for adapters, currently only nokogiri supported
2
3
 
3
4
  module AdapterHelper
@@ -9,13 +10,14 @@ module AdapterHelper
9
10
  def is_rgb?(color)
10
11
  pattern = %r{
11
12
  rgb
12
- \(\s* # literal open, with optional whitespace
13
- (\d{1,3}) # capture 1-3 digits
14
- \s*,\s* # comma, with optional whitespace
15
- (\d{1,3}) # capture 1-3 digits
16
- \s*,\s* # comma, with optional whitespace
17
- (\d{1,3}) # capture 1-3 digits
18
- \s*\) # literal close, with optional whitespace
13
+ \(\s* # literal open, with optional whitespace
14
+ (\d{1,3}) # capture 1-3 digits
15
+ (?:\s*,\s*|\s+) # comma or whitespace
16
+ (\d{1,3}) # capture 1-3 digits
17
+ (?:\s*,\s*|\s+) # comma or whitespacee
18
+ (\d{1,3}) # capture 1-3 digits
19
+ \s*(?:\/\s*\d*\.?\d*%?)? # optional alpha modifier
20
+ \s*\) # literal close, with optional whitespace
19
21
  }x
20
22
 
21
23
  pattern.match(color)
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  class Premailer
2
3
  # Manages the adapter classes. Currently supports:
3
4
  #
@@ -14,7 +15,7 @@ class Premailer
14
15
  REQUIREMENT_MAP = [
15
16
  ["nokogiri", :nokogiri],
16
17
  ["nokogiri", :nokogiri_fast],
17
- ["nokogumbo", :nokogumbo],
18
+ ["nokogumbo", :nokogumbo]
18
19
  ]
19
20
 
20
21
  # Returns the adapter to use.
@@ -42,7 +43,7 @@ class Premailer
42
43
  end
43
44
  end
44
45
 
45
- raise RuntimeError.new("No suitable adapter for Premailer was found, please install nokogiri or nokogumbo")
46
+ raise "No suitable adapter for Premailer was found, please install nokogiri or nokogumbo"
46
47
  end
47
48
 
48
49
  # Sets the adapter to use.
@@ -56,7 +57,7 @@ class Premailer
56
57
  def self.find(adapter)
57
58
  return adapter if adapter.is_a?(Module)
58
59
 
59
- Premailer::Adapter.const_get("#{adapter.to_s.split('_').map{|s| s.capitalize}.join('')}")
60
+ Premailer::Adapter.const_get("#{adapter.to_s.split('_').map{|s| s.capitalize}.join}")
60
61
  rescue NameError
61
62
  raise ArgumentError, "Invalid adapter: #{adapter}"
62
63
  end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+ class Premailer
3
+ class CachedRuleSet < CssParser::RuleSet
4
+ # we call this early to find errors but css-parser calls it in .merge again
5
+ # so to prevent slowdown and bugs we refuse to run it twice on the same ruleset
6
+ # ideally should be upstreamed into css-parser
7
+ def expand_shorthand!
8
+ super unless @expand_shorthand
9
+ ensure
10
+ @expand_shorthand = true
11
+ end
12
+ end
13
+ end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'optparse'
2
3
  require 'premailer'
3
4
 
@@ -8,12 +9,12 @@ options = {
8
9
  :remove_classes => false,
9
10
  :verbose => false,
10
11
  :line_length => 65,
11
- :adapter => :nokogiri,
12
+ :adapter => :nokogiri
12
13
  }
13
14
 
14
15
  mode = :html
15
16
 
16
- opts = OptionParser.new do |opts|
17
+ parser = OptionParser.new do |opts|
17
18
  opts.banner = "Improve the rendering of HTML emails by making CSS inline among other things. Takes a path to a local file, a URL or a pipe as input.\n\n"
18
19
  opts.define_head "Usage: premailer <optional uri|optional path> [options]"
19
20
  opts.separator ""
@@ -79,7 +80,7 @@ opts = OptionParser.new do |opts|
79
80
  exit
80
81
  end
81
82
  end
82
- opts.parse!
83
+ parser.parse!
83
84
 
84
85
  $stderr.puts "Processing in #{mode} mode with options #{options.inspect}" if options[:verbose]
85
86
 
@@ -98,7 +99,7 @@ end
98
99
  if input
99
100
  premailer = Premailer.new(input, options)
100
101
  else
101
- puts opts
102
+ puts parser
102
103
  exit 1
103
104
  end
104
105
 
@@ -1,4 +1,5 @@
1
1
  # coding: utf-8
2
+ # frozen_string_literal: true
2
3
  require 'htmlentities'
3
4
 
4
5
  # Support functions for Premailer
@@ -13,8 +14,8 @@ module HtmlToPlainText
13
14
  #
14
15
  # TODO: add support for DL, OL
15
16
  # TODO: this is not safe and needs a real html parser to work
16
- def convert_to_text(html, line_length = 65, from_charset = 'UTF-8')
17
- txt = html
17
+ def convert_to_text(html, line_length = 65, _from_charset = 'UTF-8')
18
+ txt = +html
18
19
 
19
20
  # strip text ignored html. Useful for removing
20
21
  # headers and footers that aren't needed in the
@@ -50,7 +51,7 @@ module HtmlToPlainText
50
51
 
51
52
  if text.empty?
52
53
  ''
53
- elsif href.nil? || text.strip.downcase == href.strip.downcase
54
+ elsif href.nil? || text.strip.casecmp(href.strip) == 0
54
55
  text.strip
55
56
  else
56
57
  text.strip + ' ( ' + href.strip + ' )'
@@ -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
  #
@@ -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']
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.
@@ -200,6 +202,7 @@ class Premailer
200
202
  :verbose => false,
201
203
  :debug => false,
202
204
  :io_exceptions => false,
205
+ :rule_set_exceptions => true,
203
206
  :include_link_tags => true,
204
207
  :include_style_tags => true,
205
208
  :input_encoding => 'ASCII-8BIT',
@@ -210,8 +213,7 @@ class Premailer
210
213
  :create_shorthands => true,
211
214
  :html_fragment => false,
212
215
  :adapter => Adapter.use,
213
- :drop_unmergeable_css_rules => false
214
- }.merge(options)
216
+ :drop_unmergeable_css_rules => false}.merge(options)
215
217
 
216
218
  @html_file = html
217
219
  @is_local_file = @options[:with_html_string] || Premailer.local_data?(html)
@@ -233,7 +235,8 @@ class Premailer
233
235
  @css_parser = CssParser::Parser.new({
234
236
  :absolute_paths => true,
235
237
  :import => true,
236
- :io_exceptions => @options[:io_exceptions]
238
+ :io_exceptions => @options[:io_exceptions],
239
+ :rule_set_exceptions => @options[:rule_set_exceptions]
237
240
  })
238
241
 
239
242
  @adapter_class = Adapter.find @options[:adapter]
@@ -261,11 +264,11 @@ class Premailer
261
264
 
262
265
  protected
263
266
  def load_css_from_local_file!(path)
264
- css_block = ''
265
- path.gsub!(/\Afile:/, '')
267
+ css_block = +''
268
+ path.delete_prefix!('file:')
266
269
  begin
267
270
  File.open(path, "r") do |file|
268
- while line = file.gets
271
+ while (line = file.gets)
269
272
  css_block << line
270
273
  end
271
274
  end
@@ -352,11 +355,12 @@ public
352
355
  media_types.split(/[\s]+|,/).any? { |media_type| media_type.strip =~ /screen|handheld|all/i }
353
356
  end
354
357
 
355
- def append_query_string(doc, qs)
356
- return doc if qs.nil?
358
+ def append_query_string(doc, queries)
359
+ return doc if queries.nil?
357
360
 
358
- qs.to_s.gsub!(/^[\?]*/, '').strip!
359
- return doc if qs.empty?
361
+ queries = +queries
362
+ queries.to_s.gsub!(/^[\?]*/, '').strip!
363
+ return doc if queries.empty?
360
364
 
361
365
  begin
362
366
  current_host = @base_url.host
@@ -364,18 +368,18 @@ public
364
368
  current_host = nil
365
369
  end
366
370
 
367
- $stderr.puts "Attempting to append_query_string: #{qs}" if @options[:verbose]
371
+ $stderr.puts "Attempting to append_query_string: #{queries}" if @options[:verbose]
368
372
 
369
- doc.search('a').each do|el|
373
+ doc.search('a').each do |el|
370
374
  href = el.attributes['href'].to_s.strip
371
375
  next if href.nil? or href.empty?
372
376
 
373
- next if href[0,1] =~ /[\#\{\[\<\%]/ # don't bother with anchors or special-looking links
377
+ next if /[\#\{\[\<\%]/.match?(href[0,1]) # don't bother with anchors or special-looking links
374
378
 
375
379
  begin
376
380
  href = Addressable::URI.parse(href)
377
381
 
378
- if current_host and href.host != nil and href.host != current_host
382
+ if current_host and !href.host.nil? and href.host != current_host
379
383
  $stderr.puts "Skipping append_query_string for: #{href.to_s} because host is no good" if @options[:verbose]
380
384
  next
381
385
  end
@@ -387,9 +391,9 @@ public
387
391
 
388
392
  if href.query and not href.query.empty?
389
393
  amp = @options[:unescaped_ampersand] ? '&' : '&amp;'
390
- href.query = href.query + amp + qs
394
+ href.query = href.query + amp + queries
391
395
  else
392
- href.query = qs
396
+ href.query = queries
393
397
  end
394
398
 
395
399
  el['href'] = href.to_s
@@ -432,11 +436,11 @@ public
432
436
  tags.each do |tag|
433
437
  # skip links that look like they have merge tags
434
438
  # and mailto, ftp, etc...
435
- if tag.attributes[attribute].to_s =~ /^([\%\<\{\#\[]|data:|tel:|file:|sms:|callto:|facetime:|mailto:|ftp:|gopher:|cid:)/i
439
+ if /^([\%\<\{\#\[]|data:|tel:|file:|sms:|callto:|facetime:|mailto:|ftp:|gopher:|cid:)/i.match?(tag.attributes[attribute].to_s)
436
440
  next
437
441
  end
438
442
 
439
- if tag.attributes[attribute].to_s =~ /^http/i
443
+ if /^http/i.match?(tag.attributes[attribute].to_s)
440
444
  begin
441
445
  merged = Addressable::URI.parse(tag.attributes[attribute])
442
446
  rescue; next; end
@@ -470,9 +474,10 @@ public
470
474
 
471
475
  # @private
472
476
  def self.resolve_link(path, base_path) # :nodoc:
477
+ path = +path
473
478
  path.strip!
474
479
  resolved = nil
475
- if path =~ /\A(?:(https?|ftp|file):)\/\//i
480
+ if /\A(?:(https?|ftp|file):)\/\//i.match?(path)
476
481
  resolved = path
477
482
  Premailer.canonicalize(resolved)
478
483
  elsif base_path.kind_of?(Addressable::URI)
@@ -504,7 +509,8 @@ public
504
509
 
505
510
  # Check <tt>CLIENT_SUPPORT_FILE</tt> for any CSS warnings
506
511
  def check_client_support # :nodoc:
507
- @client_support ||= YAML::load(File.open(CLIENT_SUPPORT_FILE))
512
+ kwargs = Psych::VERSION >= '4' ? { aliases: true } : {}
513
+ @client_support ||= Psych.load(File.open(CLIENT_SUPPORT_FILE), **kwargs)
508
514
 
509
515
  warnings = []
510
516
  properties = []
@@ -1,4 +1,5 @@
1
+ # frozen_string_literal: true
1
2
  class Premailer
2
3
  # Premailer version.
3
- VERSION = '1.16.0'.freeze
4
+ VERSION = '1.27.0'
4
5
  end
data/lib/premailer.rb CHANGED
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'yaml'
2
3
  require 'open-uri'
3
4
  require 'digest/sha2'
@@ -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'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: premailer
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.16.0
4
+ version: 1.27.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alex Dunae
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-05-04 00:00:00.000000000 Z
11
+ date: 2024-08-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: css_parser
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: 1.6.0
19
+ version: 1.19.0
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: 1.6.0
26
+ version: 1.19.0
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: htmlentities
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -92,14 +92,14 @@ dependencies:
92
92
  requirements:
93
93
  - - "~>"
94
94
  - !ruby/object:Gem::Version
95
- version: '1.13'
95
+ version: '1.16'
96
96
  type: :development
97
97
  prerelease: false
98
98
  version_requirements: !ruby/object:Gem::Requirement
99
99
  requirements:
100
100
  - - "~>"
101
101
  - !ruby/object:Gem::Version
102
- version: '1.13'
102
+ version: '1.16'
103
103
  - !ruby/object:Gem::Dependency
104
104
  name: redcarpet
105
105
  requirement: !ruby/object:Gem::Requirement
@@ -129,7 +129,7 @@ dependencies:
129
129
  - !ruby/object:Gem::Version
130
130
  version: '0'
131
131
  - !ruby/object:Gem::Dependency
132
- name: coveralls
132
+ name: webmock
133
133
  requirement: !ruby/object:Gem::Requirement
134
134
  requirements:
135
135
  - - ">="
@@ -143,7 +143,7 @@ dependencies:
143
143
  - !ruby/object:Gem::Version
144
144
  version: '0'
145
145
  - !ruby/object:Gem::Dependency
146
- name: webmock
146
+ name: bump
147
147
  requirement: !ruby/object:Gem::Requirement
148
148
  requirements:
149
149
  - - ">="
@@ -157,7 +157,21 @@ dependencies:
157
157
  - !ruby/object:Gem::Version
158
158
  version: '0'
159
159
  - !ruby/object:Gem::Dependency
160
- name: bump
160
+ name: rubocop
161
+ requirement: !ruby/object:Gem::Requirement
162
+ requirements:
163
+ - - "~>"
164
+ - !ruby/object:Gem::Version
165
+ version: 1.62.1
166
+ type: :development
167
+ prerelease: false
168
+ version_requirements: !ruby/object:Gem::Requirement
169
+ requirements:
170
+ - - "~>"
171
+ - !ruby/object:Gem::Version
172
+ version: 1.62.1
173
+ - !ruby/object:Gem::Dependency
174
+ name: rubocop-performance
161
175
  requirement: !ruby/object:Gem::Requirement
162
176
  requirements:
163
177
  - - ">="
@@ -187,6 +201,7 @@ files:
187
201
  - lib/premailer/adapter/nokogiri_fast.rb
188
202
  - lib/premailer/adapter/nokogumbo.rb
189
203
  - lib/premailer/adapter/rgb_to_hex.rb
204
+ - lib/premailer/cached_rule_set.rb
190
205
  - lib/premailer/executor.rb
191
206
  - lib/premailer/html_to_plain_text.rb
192
207
  - lib/premailer/premailer.rb
@@ -197,7 +212,8 @@ licenses:
197
212
  - BSD-3-Clause
198
213
  metadata:
199
214
  yard.run: yri
200
- post_install_message:
215
+ rubygems_mfa_required: 'true'
216
+ post_install_message:
201
217
  rdoc_options: []
202
218
  require_paths:
203
219
  - lib
@@ -205,15 +221,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
205
221
  requirements:
206
222
  - - ">="
207
223
  - !ruby/object:Gem::Version
208
- version: 2.7.0
224
+ version: '3.0'
209
225
  required_rubygems_version: !ruby/object:Gem::Requirement
210
226
  requirements:
211
227
  - - ">="
212
228
  - !ruby/object:Gem::Version
213
229
  version: '0'
214
230
  requirements: []
215
- rubygems_version: 3.1.6
216
- signing_key:
231
+ rubygems_version: 3.4.10
232
+ signing_key:
217
233
  specification_version: 4
218
234
  summary: Preflight for HTML e-mail.
219
235
  test_files: []