premailer 1.23.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 +4 -4
- data/README.md +49 -29
- data/bin/premailer +1 -1
- data/lib/premailer/adapter/nokogiri.rb +11 -8
- data/lib/premailer/adapter/nokogiri_fast.rb +12 -10
- data/lib/premailer/adapter/nokogumbo.rb +10 -8
- data/lib/premailer/adapter/rgb_to_hex.rb +1 -0
- data/lib/premailer/adapter.rb +4 -3
- data/lib/premailer/cached_rule_set.rb +1 -0
- data/lib/premailer/executor.rb +5 -4
- data/lib/premailer/html_to_plain_text.rb +4 -3
- data/lib/premailer/premailer.rb +22 -20
- data/lib/premailer/version.rb +2 -1
- data/lib/premailer.rb +1 -0
- metadata +21 -7
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 31108244f6afc74508eef1337a9b99adc7f2643b5412e2aacf808ecb8c520101
|
|
4
|
+
data.tar.gz: b2f10188d4b7143050eb86e04776c38fc220ebb834da7871e838fe47db5fe9c5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
-
|
|
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](
|
|
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', :
|
|
28
|
+
premailer = Premailer.new('http://example.com/myfile.html', warn_level: Premailer::Warnings::SAFE)
|
|
33
29
|
|
|
34
|
-
# Write the plain-text output
|
|
35
|
-
|
|
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.
|
|
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
|
|
48
|
+
(hpricot adapter removed, use `~>1.9.0` version if you need it)
|
|
62
49
|
|
|
63
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
91
|
+
For example
|
|
103
92
|
|
|
104
|
-
|
|
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
|
-
|
|
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
|
-
[
|
|
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.
|
|
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,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
|
|
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,8 +63,8 @@ class Premailer
|
|
|
61
63
|
doc.search("*[@style]").each do |el|
|
|
62
64
|
style = el.attributes['style'].to_s
|
|
63
65
|
|
|
64
|
-
declarations = style.scan(/\[SPEC\=([\d]+)\[(.[^\]\]]*)\]\]/).filter_map do |declaration|
|
|
65
|
-
rs = Premailer::CachedRuleSet.new(
|
|
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)
|
|
66
68
|
rs.expand_shorthand!
|
|
67
69
|
rs
|
|
68
70
|
rescue ArgumentError => e
|
|
@@ -90,7 +92,7 @@ class Premailer
|
|
|
90
92
|
new_val.gsub!(/;$|\s*!important/, '').strip!
|
|
91
93
|
|
|
92
94
|
# For width and height tags, remove px units
|
|
93
|
-
new_val.gsub!(/(\d+)px/, '\1') if
|
|
95
|
+
new_val.gsub!(/(\d+)px/, '\1') if WIDTH_AND_HIGHT.include?(html_attr)
|
|
94
96
|
|
|
95
97
|
# For color-related tags, convert RGB to hex if specified by options
|
|
96
98
|
new_val = ensure_hex(new_val) if css_attr.end_with?('color') && @options[:rgb_to_hex_attributes]
|
|
@@ -99,7 +101,7 @@ class Premailer
|
|
|
99
101
|
end
|
|
100
102
|
|
|
101
103
|
unless @options[:preserve_style_attribute]
|
|
102
|
-
merged.instance_variable_get(
|
|
104
|
+
merged.instance_variable_get(:@declarations).tap do |declarations|
|
|
103
105
|
declarations.delete(css_attr)
|
|
104
106
|
end
|
|
105
107
|
end
|
|
@@ -235,6 +237,7 @@ class Premailer
|
|
|
235
237
|
|
|
236
238
|
# Handle HTML entities
|
|
237
239
|
if @options[:replace_html_entities] == true and thing.is_a?(String)
|
|
240
|
+
thing = +thing
|
|
238
241
|
HTML_ENTITIES.map do |entity, replacement|
|
|
239
242
|
thing.gsub! entity, replacement
|
|
240
243
|
end
|
|
@@ -249,7 +252,7 @@ class Premailer
|
|
|
249
252
|
# Fix for removing any CDATA tags from both style and script tags inserted per
|
|
250
253
|
# https://github.com/sparklemotion/nokogiri/issues/311 and
|
|
251
254
|
# https://github.com/premailer/premailer/issues/199
|
|
252
|
-
|
|
255
|
+
['style', 'script'].each do |tag|
|
|
253
256
|
doc.search(tag).children.each do |child|
|
|
254
257
|
child.swap(child.text()) if child.cdata?
|
|
255
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
|
|
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,9 +68,9 @@ class Premailer
|
|
|
66
68
|
style = el.attributes['style'].to_s
|
|
67
69
|
|
|
68
70
|
declarations = []
|
|
69
|
-
style.scan(/\[SPEC\=([\d]+)\[(.[^\]\]]*)\]\]/).each do |declaration|
|
|
71
|
+
style.scan(/\[SPEC\=([\d]+)\[(.[^\]\]]*)\]\]/m).each do |declaration|
|
|
70
72
|
begin
|
|
71
|
-
rs = CssParser::RuleSet.new(
|
|
73
|
+
rs = CssParser::RuleSet.new(block: declaration[1].to_s, specificity: declaration[0].to_i)
|
|
72
74
|
declarations << rs
|
|
73
75
|
rescue ArgumentError => e
|
|
74
76
|
raise e if @options[:rule_set_exceptions]
|
|
@@ -96,7 +98,7 @@ class Premailer
|
|
|
96
98
|
new_val.gsub!(/;$|\s*!important/, '').strip!
|
|
97
99
|
|
|
98
100
|
# For width and height tags, remove px units
|
|
99
|
-
new_val.gsub!(/(\d+)px/, '\1') if
|
|
101
|
+
new_val.gsub!(/(\d+)px/, '\1') if WIDTH_AND_HEIGHT.include?(html_attr)
|
|
100
102
|
|
|
101
103
|
# For color-related tags, convert RGB to hex if specified by options
|
|
102
104
|
new_val = ensure_hex(new_val) if css_attr.end_with?('color') && @options[:rgb_to_hex_attributes]
|
|
@@ -105,7 +107,7 @@ class Premailer
|
|
|
105
107
|
end
|
|
106
108
|
|
|
107
109
|
unless @options[:preserve_style_attribute]
|
|
108
|
-
merged.instance_variable_get(
|
|
110
|
+
merged.instance_variable_get(:@declarations).tap do |declarations|
|
|
109
111
|
declarations.delete(css_attr)
|
|
110
112
|
end
|
|
111
113
|
end
|
|
@@ -255,7 +257,7 @@ class Premailer
|
|
|
255
257
|
# Fix for removing any CDATA tags from both style and script tags inserted per
|
|
256
258
|
# https://github.com/sparklemotion/nokogiri/issues/311 and
|
|
257
259
|
# https://github.com/premailer/premailer/issues/199
|
|
258
|
-
|
|
260
|
+
['style', 'script'].each do |tag|
|
|
259
261
|
doc.search(tag).children.each do |child|
|
|
260
262
|
child.swap(child.text()) if child.cdata?
|
|
261
263
|
end
|
|
@@ -314,7 +316,7 @@ class Premailer
|
|
|
314
316
|
index.default = Set.new
|
|
315
317
|
descendants.default = Set.new
|
|
316
318
|
|
|
317
|
-
|
|
319
|
+
[index, Set.new(all_nodes), descendants]
|
|
318
320
|
end
|
|
319
321
|
|
|
320
322
|
# @param doc The top level document
|
|
@@ -354,7 +356,7 @@ class Premailer
|
|
|
354
356
|
# It will return nil when such a selector is passed, so you can take
|
|
355
357
|
# action on the falsity of the return value.
|
|
356
358
|
def match_selector(index, all_nodes, descendants, selector)
|
|
357
|
-
if /[^-a-zA-Z0-9_\s.#]/.match(selector) then
|
|
359
|
+
if /[^-a-zA-Z0-9_\s.#]/.match?(selector) then
|
|
358
360
|
return nil
|
|
359
361
|
end
|
|
360
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
|
|
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,9 +62,9 @@ class Premailer
|
|
|
60
62
|
style = el.attributes['style'].to_s
|
|
61
63
|
|
|
62
64
|
declarations = []
|
|
63
|
-
style.scan(/\[SPEC\=([\d]+)\[(.[^\]\]]*)\]\]/).each do |declaration|
|
|
65
|
+
style.scan(/\[SPEC\=([\d]+)\[(.[^\]\]]*)\]\]/m).each do |declaration|
|
|
64
66
|
begin
|
|
65
|
-
rs = CssParser::RuleSet.new(
|
|
67
|
+
rs = CssParser::RuleSet.new(block: declaration[1].to_s, specificity: declaration[0].to_i)
|
|
66
68
|
declarations << rs
|
|
67
69
|
rescue ArgumentError => e
|
|
68
70
|
raise e if @options[:rule_set_exceptions]
|
|
@@ -90,7 +92,7 @@ class Premailer
|
|
|
90
92
|
new_val.gsub!(/;$|\s*!important/, '').strip!
|
|
91
93
|
|
|
92
94
|
# For width and height tags, remove px units
|
|
93
|
-
new_val.gsub!(/(\d+)px/, '\1') if
|
|
95
|
+
new_val.gsub!(/(\d+)px/, '\1') if WIDTH_AND_HEIGHT.include?(html_attr)
|
|
94
96
|
|
|
95
97
|
# For color-related tags, convert RGB to hex if specified by options
|
|
96
98
|
new_val = ensure_hex(new_val) if css_attr.end_with?('color') && @options[:rgb_to_hex_attributes]
|
|
@@ -99,7 +101,7 @@ class Premailer
|
|
|
99
101
|
end
|
|
100
102
|
|
|
101
103
|
unless @options[:preserve_style_attribute]
|
|
102
|
-
merged.instance_variable_get(
|
|
104
|
+
merged.instance_variable_get(:@declarations).tap do |declarations|
|
|
103
105
|
declarations.delete(css_attr)
|
|
104
106
|
end
|
|
105
107
|
end
|
|
@@ -248,7 +250,7 @@ class Premailer
|
|
|
248
250
|
# Fix for removing any CDATA tags from both style and script tags inserted per
|
|
249
251
|
# https://github.com/sparklemotion/nokogiri/issues/311 and
|
|
250
252
|
# https://github.com/premailer/premailer/issues/199
|
|
251
|
-
|
|
253
|
+
['style', 'script'].each do |tag|
|
|
252
254
|
doc.search(tag).children.each do |child|
|
|
253
255
|
child.swap(child.text()) if child.cdata?
|
|
254
256
|
end
|
data/lib/premailer/adapter.rb
CHANGED
|
@@ -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
|
|
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
|
data/lib/premailer/executor.rb
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
|
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,
|
|
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.
|
|
54
|
+
elsif href.nil? || text.strip.casecmp(href.strip) == 0
|
|
54
55
|
text.strip
|
|
55
56
|
else
|
|
56
57
|
text.strip + ' ( ' + href.strip + ' )'
|
data/lib/premailer/premailer.rb
CHANGED
|
@@ -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("
|
|
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 =
|
|
145
|
+
WARN_LABEL = ['NONE', 'SAFE', 'POOR', 'RISKY']
|
|
145
146
|
|
|
146
147
|
# Create a new Premailer object.
|
|
147
148
|
#
|
|
@@ -212,8 +213,7 @@ class Premailer
|
|
|
212
213
|
:create_shorthands => true,
|
|
213
214
|
:html_fragment => false,
|
|
214
215
|
:adapter => Adapter.use,
|
|
215
|
-
:drop_unmergeable_css_rules => false
|
|
216
|
-
}.merge(options)
|
|
216
|
+
:drop_unmergeable_css_rules => false}.merge(options)
|
|
217
217
|
|
|
218
218
|
@html_file = html
|
|
219
219
|
@is_local_file = @options[:with_html_string] || Premailer.local_data?(html)
|
|
@@ -264,11 +264,11 @@ class Premailer
|
|
|
264
264
|
|
|
265
265
|
protected
|
|
266
266
|
def load_css_from_local_file!(path)
|
|
267
|
-
css_block = ''
|
|
268
|
-
path.
|
|
267
|
+
css_block = +''
|
|
268
|
+
path.delete_prefix!('file:')
|
|
269
269
|
begin
|
|
270
270
|
File.open(path, "r") do |file|
|
|
271
|
-
while line = file.gets
|
|
271
|
+
while (line = file.gets)
|
|
272
272
|
css_block << line
|
|
273
273
|
end
|
|
274
274
|
end
|
|
@@ -355,11 +355,12 @@ public
|
|
|
355
355
|
media_types.split(/[\s]+|,/).any? { |media_type| media_type.strip =~ /screen|handheld|all/i }
|
|
356
356
|
end
|
|
357
357
|
|
|
358
|
-
def append_query_string(doc,
|
|
359
|
-
return doc if
|
|
358
|
+
def append_query_string(doc, queries)
|
|
359
|
+
return doc if queries.nil?
|
|
360
360
|
|
|
361
|
-
|
|
362
|
-
|
|
361
|
+
queries = +queries
|
|
362
|
+
queries.to_s.gsub!(/^[\?]*/, '').strip!
|
|
363
|
+
return doc if queries.empty?
|
|
363
364
|
|
|
364
365
|
begin
|
|
365
366
|
current_host = @base_url.host
|
|
@@ -367,18 +368,18 @@ public
|
|
|
367
368
|
current_host = nil
|
|
368
369
|
end
|
|
369
370
|
|
|
370
|
-
$stderr.puts "Attempting to append_query_string: #{
|
|
371
|
+
$stderr.puts "Attempting to append_query_string: #{queries}" if @options[:verbose]
|
|
371
372
|
|
|
372
|
-
doc.search('a').each do|el|
|
|
373
|
+
doc.search('a').each do |el|
|
|
373
374
|
href = el.attributes['href'].to_s.strip
|
|
374
375
|
next if href.nil? or href.empty?
|
|
375
376
|
|
|
376
|
-
next if
|
|
377
|
+
next if /[\#\{\[\<\%]/.match?(href[0,1]) # don't bother with anchors or special-looking links
|
|
377
378
|
|
|
378
379
|
begin
|
|
379
380
|
href = Addressable::URI.parse(href)
|
|
380
381
|
|
|
381
|
-
if current_host and href.host
|
|
382
|
+
if current_host and !href.host.nil? and href.host != current_host
|
|
382
383
|
$stderr.puts "Skipping append_query_string for: #{href.to_s} because host is no good" if @options[:verbose]
|
|
383
384
|
next
|
|
384
385
|
end
|
|
@@ -390,9 +391,9 @@ public
|
|
|
390
391
|
|
|
391
392
|
if href.query and not href.query.empty?
|
|
392
393
|
amp = @options[:unescaped_ampersand] ? '&' : '&'
|
|
393
|
-
href.query = href.query + amp +
|
|
394
|
+
href.query = href.query + amp + queries
|
|
394
395
|
else
|
|
395
|
-
href.query =
|
|
396
|
+
href.query = queries
|
|
396
397
|
end
|
|
397
398
|
|
|
398
399
|
el['href'] = href.to_s
|
|
@@ -435,11 +436,11 @@ public
|
|
|
435
436
|
tags.each do |tag|
|
|
436
437
|
# skip links that look like they have merge tags
|
|
437
438
|
# and mailto, ftp, etc...
|
|
438
|
-
if
|
|
439
|
+
if /^([\%\<\{\#\[]|data:|tel:|file:|sms:|callto:|facetime:|mailto:|ftp:|gopher:|cid:)/i.match?(tag.attributes[attribute].to_s)
|
|
439
440
|
next
|
|
440
441
|
end
|
|
441
442
|
|
|
442
|
-
if tag.attributes[attribute].to_s
|
|
443
|
+
if /^http/i.match?(tag.attributes[attribute].to_s)
|
|
443
444
|
begin
|
|
444
445
|
merged = Addressable::URI.parse(tag.attributes[attribute])
|
|
445
446
|
rescue; next; end
|
|
@@ -473,9 +474,10 @@ public
|
|
|
473
474
|
|
|
474
475
|
# @private
|
|
475
476
|
def self.resolve_link(path, base_path) # :nodoc:
|
|
477
|
+
path = +path
|
|
476
478
|
path.strip!
|
|
477
479
|
resolved = nil
|
|
478
|
-
if
|
|
480
|
+
if /\A(?:(https?|ftp|file):)\/\//i.match?(path)
|
|
479
481
|
resolved = path
|
|
480
482
|
Premailer.canonicalize(resolved)
|
|
481
483
|
elsif base_path.kind_of?(Addressable::URI)
|
data/lib/premailer/version.rb
CHANGED
data/lib/premailer.rb
CHANGED
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.
|
|
4
|
+
version: 1.27.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Alex Dunae
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2024-
|
|
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.
|
|
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.
|
|
26
|
+
version: 1.19.0
|
|
27
27
|
- !ruby/object:Gem::Dependency
|
|
28
28
|
name: htmlentities
|
|
29
29
|
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:
|
|
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:
|
|
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:
|
|
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
|
- - ">="
|