premailer 1.32.0 → 1.33.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 +42 -0
- data/lib/premailer/adapter/nokogiri.rb +22 -1
- data/lib/premailer/adapter/nokogiri_fast.rb +6 -1
- data/lib/premailer/adapter/nokogumbo.rb +3 -0
- data/lib/premailer/html_to_plain_text.rb +6 -7
- data/lib/premailer/premailer.rb +25 -2
- data/lib/premailer/version.rb +1 -1
- metadata +3 -17
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b30285e2878d409bc44b84777bc055096cd023b666dd7453164586fb8e97a7dd
|
|
4
|
+
data.tar.gz: 47bfb72f292747619e4067332275f40f85bd225725977507a57e97bebffbdd71
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 77556f76f97fa85e0047b1e45ebdd530853d3cc33794366b3979b4475920a6b87163853d8349b2f507e6fbafa24a2a747529f448f3335b3b03446713266b3ab2
|
|
7
|
+
data.tar.gz: 50ac02894d5fef9ccc6e5b8df0e75feac8dcedff61eacdca4fac43230f9802319907648377666f132e19da46585965020adf128d70d0a7e7d12348d70103f7d9
|
data/README.md
CHANGED
|
@@ -39,6 +39,9 @@ premailer.warnings.each do |w|
|
|
|
39
39
|
end
|
|
40
40
|
```
|
|
41
41
|
|
|
42
|
+
The plain-text version excludes the contents of `<style>` and `<script>`
|
|
43
|
+
elements, including styles retained in HTML with `preserve_styles: true`.
|
|
44
|
+
|
|
42
45
|
## Adapters
|
|
43
46
|
|
|
44
47
|
1. nokogiri (default)
|
|
@@ -53,6 +56,10 @@ Picking an adapter:
|
|
|
53
56
|
Premailer::Adapter.use = :nokogiri_fast
|
|
54
57
|
```
|
|
55
58
|
|
|
59
|
+
When converting image CSS to HTML attributes, only `float: left` and
|
|
60
|
+
`float: right` become `align` attributes. Other values, such as `float: none`,
|
|
61
|
+
remain in the inline style because they have no equivalent HTML alignment.
|
|
62
|
+
|
|
56
63
|
## Ruby Compatibility
|
|
57
64
|
|
|
58
65
|
See .github/workflows/actions.yml for which ruby versions are tested. JRuby support is close, contributors are welcome.
|
|
@@ -72,6 +79,9 @@ Premailer looks for a few CSS attributes that make working with tables a bit eas
|
|
|
72
79
|
|
|
73
80
|
Each of these CSS declarations will be copied to appropriate element's attribute.
|
|
74
81
|
|
|
82
|
+
Background image URLs on tables are copied to the HTML `background` attribute.
|
|
83
|
+
Gradients remain in inline CSS because that attribute only supports image URLs.
|
|
84
|
+
|
|
75
85
|
For example
|
|
76
86
|
|
|
77
87
|
```css
|
|
@@ -127,6 +137,10 @@ Premailer.new(
|
|
|
127
137
|
|
|
128
138
|
[available options](https://premailer.github.io/premailer/Premailer.html#initialize-instance_method)
|
|
129
139
|
|
|
140
|
+
For the current options, see the [constructor source](https://github.com/premailer/premailer/blob/master/lib/premailer/premailer.rb).
|
|
141
|
+
|
|
142
|
+
Verbose diagnostics are written to stderr. The command-line tool keeps its generated HTML on stdout, including when `--verbose` is enabled.
|
|
143
|
+
|
|
130
144
|
### Loading CSS from local/private hosts
|
|
131
145
|
|
|
132
146
|
css_parser 3+ blocks fetching remote CSS (`<link>`, `@import`) from hosts that resolve to loopback/private IPs
|
|
@@ -137,6 +151,11 @@ To allow them, when the html is trusted:
|
|
|
137
151
|
Premailer.new(html, with_html_string: true, allow_local_network: true)
|
|
138
152
|
```
|
|
139
153
|
|
|
154
|
+
### Subresource Integrity
|
|
155
|
+
|
|
156
|
+
Remote `<link rel="stylesheet">` tags with an `integrity` attribute are verified before inlining.
|
|
157
|
+
A stylesheet whose hash does not match is dropped (or raises `CssParser::IntegrityError` with `io_exceptions: true`).
|
|
158
|
+
|
|
140
159
|
## Support for CSS variables
|
|
141
160
|
|
|
142
161
|
The gem does not automatically replace CSS variables with their static values.
|
|
@@ -219,6 +238,29 @@ A few areas that are particularly in need of love:
|
|
|
219
238
|
* Improved test coverage
|
|
220
239
|
* Move un-repeated background images defined in CSS for Outlook
|
|
221
240
|
|
|
241
|
+
## Generating API documentation
|
|
242
|
+
|
|
243
|
+
Generate the API reference with [YARD](https://yardoc.org/) from the repository root:
|
|
244
|
+
|
|
245
|
+
```sh
|
|
246
|
+
bundle install
|
|
247
|
+
bundle exec yard doc
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
Open the generated files in a browser. On macOS:
|
|
251
|
+
|
|
252
|
+
```sh
|
|
253
|
+
open doc/index.html # all docs
|
|
254
|
+
open doc/Premailer.html # constructor and its options
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
The generated `doc/` and `.yardoc/` directories are ignored by Git.
|
|
258
|
+
|
|
259
|
+
To update the [hosted documentation](https://premailer.github.io/premailer/), a maintainer
|
|
260
|
+
regenerates the reference for the desired version and copies the contents of `doc/` to
|
|
261
|
+
the root of the [`gh-pages` branch](https://github.com/premailer/premailer/tree/gh-pages).
|
|
262
|
+
After reviewing, committing, and pushing those changes, GitHub Pages publishes that branch's root.
|
|
263
|
+
|
|
222
264
|
## Credits and code
|
|
223
265
|
|
|
224
266
|
Thanks to [all the wonderful contributors](https://github.com/premailer/premailer/contributors) for their updates.
|
|
@@ -6,6 +6,11 @@ class Premailer
|
|
|
6
6
|
# Nokogiri adapter
|
|
7
7
|
module Nokogiri
|
|
8
8
|
WIDTH_AND_HIGHT = ['width', 'height'].freeze
|
|
9
|
+
# Selectors made only of element names, classes, ids, combinators and
|
|
10
|
+
# pseudo-classes without arguments: no attribute, string, function or escape
|
|
11
|
+
# can hide a "." that is not a class.
|
|
12
|
+
PLAIN_SELECTOR = /\A[\w\s.#>+~*:-]+\z/
|
|
13
|
+
CLASS_IN_SELECTOR = /\.([\w-]+)/
|
|
9
14
|
|
|
10
15
|
include AdapterHelper::RgbToHex
|
|
11
16
|
# Merge CSS into the HTML document.
|
|
@@ -31,6 +36,10 @@ class Premailer
|
|
|
31
36
|
rs = rule_set_cache.fetch(style) { rule_set_cache[style] = build_rule_set(style, 1000) }
|
|
32
37
|
rules_by_node[el] = rs ? [rs] : []
|
|
33
38
|
end
|
|
39
|
+
# A selector requiring a class that no element carries cannot match:
|
|
40
|
+
# skip its (costly) document-wide search.
|
|
41
|
+
classes_in_doc = doc.xpath('.//@class').flat_map { |attribute| attribute.value.split }.to_set
|
|
42
|
+
|
|
34
43
|
# Iterate through the rules and merge them into the HTML
|
|
35
44
|
@css_parser.each_selector(:all) do |selector, declaration, specificity, media_types|
|
|
36
45
|
# Save un-mergable rules separately
|
|
@@ -49,6 +58,8 @@ class Premailer
|
|
|
49
58
|
@unmergable_rules.add_rule_set!(CssParser::RuleSet.new(selectors: selector, block: declaration))
|
|
50
59
|
end
|
|
51
60
|
|
|
61
|
+
next if selector_requires_absent_class?(selector, classes_in_doc)
|
|
62
|
+
|
|
52
63
|
# Change single ID CSS selectors into xpath so that we can match more
|
|
53
64
|
# than one element. Added to work around dodgy generated code.
|
|
54
65
|
selector.gsub!(/\A\#([\w_\-]+)\Z/, '*[@id=\1]')
|
|
@@ -176,6 +187,9 @@ class Premailer
|
|
|
176
187
|
# Duplicate CSS attributes as HTML attributes
|
|
177
188
|
if related_el_name
|
|
178
189
|
Premailer::RELATED_ATTRIBUTES[related_el_name].each do |css_attr, html_attr|
|
|
190
|
+
next if css_gradient?(css_attr, merged)
|
|
191
|
+
next if css_float_without_alignment?(css_attr, merged)
|
|
192
|
+
|
|
179
193
|
unless merged[css_attr].empty?
|
|
180
194
|
new_val = merged[css_attr].dup
|
|
181
195
|
|
|
@@ -209,6 +223,11 @@ class Premailer
|
|
|
209
223
|
[merged.declarations_to_s, attributes]
|
|
210
224
|
end
|
|
211
225
|
|
|
226
|
+
def selector_requires_absent_class?(selector, classes_in_doc) # :nodoc:
|
|
227
|
+
PLAIN_SELECTOR.match?(selector) &&
|
|
228
|
+
selector.scan(CLASS_IN_SELECTOR).any? { |(name)| !classes_in_doc.include?(name) }
|
|
229
|
+
end
|
|
230
|
+
|
|
212
231
|
# Build an expanded rule set for folding. Returns nil (and optionally
|
|
213
232
|
# swallows the error, mirroring the old fold-time rescue) on bad CSS.
|
|
214
233
|
def build_rule_set(block, specificity) # :nodoc:
|
|
@@ -302,7 +321,9 @@ class Premailer
|
|
|
302
321
|
end
|
|
303
322
|
encoding = @options[:input_encoding] || (RUBY_PLATFORM == 'java' ? nil : 'BINARY')
|
|
304
323
|
doc = if @options[:html_fragment]
|
|
305
|
-
|
|
324
|
+
# libxml2 cannot serialize a fragment parsed as binary (sparklemotion/nokogiri#553):
|
|
325
|
+
# without an encoding, Nokogiri uses the string's own, or UTF-8 if it is binary.
|
|
326
|
+
::Nokogiri::HTML.fragment(thing, (encoding unless encoding.to_s.match?(/\A(ASCII-8BIT|BINARY)\z/i)))
|
|
306
327
|
else
|
|
307
328
|
::Nokogiri::HTML(thing, nil, encoding, &:recover)
|
|
308
329
|
end
|
|
@@ -85,6 +85,9 @@ class Premailer
|
|
|
85
85
|
# Duplicate CSS attributes as HTML attributes
|
|
86
86
|
if Premailer::RELATED_ATTRIBUTES.key?(el.name) && @options[:css_to_attributes]
|
|
87
87
|
Premailer::RELATED_ATTRIBUTES[el.name].each do |css_attr, html_attr|
|
|
88
|
+
next if css_gradient?(css_attr, merged)
|
|
89
|
+
next if css_float_without_alignment?(css_attr, merged)
|
|
90
|
+
|
|
88
91
|
if el[html_attr].nil? && !merged[css_attr].empty?
|
|
89
92
|
new_val = merged[css_attr].dup
|
|
90
93
|
|
|
@@ -245,7 +248,9 @@ class Premailer
|
|
|
245
248
|
end
|
|
246
249
|
encoding = @options[:input_encoding] || (RUBY_PLATFORM == 'java' ? nil : 'BINARY')
|
|
247
250
|
doc = if @options[:html_fragment]
|
|
248
|
-
|
|
251
|
+
# libxml2 cannot serialize a fragment parsed as binary (sparklemotion/nokogiri#553):
|
|
252
|
+
# without an encoding, Nokogiri uses the string's own, or UTF-8 if it is binary.
|
|
253
|
+
::Nokogiri::HTML.fragment(thing, (encoding unless encoding.to_s.match?(/\A(ASCII-8BIT|BINARY)\z/i)))
|
|
249
254
|
else
|
|
250
255
|
::Nokogiri::HTML(thing, nil, encoding, &:recover)
|
|
251
256
|
end
|
|
@@ -80,6 +80,9 @@ class Premailer
|
|
|
80
80
|
# Duplicate CSS attributes as HTML attributes
|
|
81
81
|
if Premailer::RELATED_ATTRIBUTES.key?(el.name) && @options[:css_to_attributes]
|
|
82
82
|
Premailer::RELATED_ATTRIBUTES[el.name].each do |css_attr, html_attr|
|
|
83
|
+
next if css_gradient?(css_attr, merged)
|
|
84
|
+
next if css_float_without_alignment?(css_attr, merged)
|
|
85
|
+
|
|
83
86
|
if el[html_attr].nil? && !merged[css_attr].empty?
|
|
84
87
|
new_val = merged[css_attr].dup
|
|
85
88
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
-
require '
|
|
2
|
+
require 'nokogiri'
|
|
3
3
|
|
|
4
4
|
# Support functions for Premailer
|
|
5
5
|
module HtmlToPlainText
|
|
@@ -20,6 +20,9 @@ module HtmlToPlainText
|
|
|
20
20
|
# text version
|
|
21
21
|
txt.gsub!(/<!-- start text\/html -->.*?<!-- end text\/html -->/m, '')
|
|
22
22
|
|
|
23
|
+
# Metadata is not readable text, even when styles are preserved in HTML.
|
|
24
|
+
txt.gsub!(/<(script|style)\b[^>]*>.*?<\/\1\s*>/im, '')
|
|
25
|
+
|
|
23
26
|
# replace images with their alt attributes
|
|
24
27
|
# for img tags with "" for attribute quotes
|
|
25
28
|
# with or without closing tag
|
|
@@ -35,9 +38,6 @@ module HtmlToPlainText
|
|
|
35
38
|
# <img alt=''>
|
|
36
39
|
txt.gsub!(/<img[^>]+?alt='([^']*)'[^>]*>/i, '\1')
|
|
37
40
|
|
|
38
|
-
# remove script tags and content
|
|
39
|
-
txt.gsub!(/<script.*?\/script>/m, '')
|
|
40
|
-
|
|
41
41
|
# links
|
|
42
42
|
txt.gsub!(/<a\s+([^>]+)>(.*?)<\/a>/im) do |s|
|
|
43
43
|
text = $2.strip
|
|
@@ -101,9 +101,8 @@ module HtmlToPlainText
|
|
|
101
101
|
# strip remaining tags
|
|
102
102
|
txt.gsub!(/<\/?[^>]*>/, '')
|
|
103
103
|
|
|
104
|
-
# decode HTML entities
|
|
105
|
-
|
|
106
|
-
txt = he.decode(txt)
|
|
104
|
+
# decode HTML entities; a textarea context parses as RCDATA, so a stray "<" stays text
|
|
105
|
+
txt = Nokogiri::HTML5::DocumentFragment.new(Nokogiri::HTML5::Document.new, txt, "textarea").text
|
|
107
106
|
|
|
108
107
|
# word wrap
|
|
109
108
|
txt = word_wrap(txt, line_length)
|
data/lib/premailer/premailer.rb
CHANGED
|
@@ -271,6 +271,29 @@ class Premailer
|
|
|
271
271
|
|
|
272
272
|
protected
|
|
273
273
|
|
|
274
|
+
# Keep e.g. background-image: image-set(linear-gradient(red, blue), url(a.png))
|
|
275
|
+
# in CSS: a URL-only HTML background attribute cannot represent its gradient.
|
|
276
|
+
def css_gradient?(css_attr, merged) # :nodoc:
|
|
277
|
+
return false unless css_attr == 'background-image'
|
|
278
|
+
|
|
279
|
+
# Ignore gradient-like text inside URLs and quoted strings.
|
|
280
|
+
value = merged[css_attr].gsub(CssParser::URI_RX, '').gsub(CssParser::RE_STRING, '')
|
|
281
|
+
value.match?(
|
|
282
|
+
/(?:\A|[,(])\s* # Start of a value, list item, or nested function
|
|
283
|
+
(?:-[a-z]+-)? # Optional vendor prefix, e.g. -webkit-
|
|
284
|
+
(?:repeating-)? # Optional repeating gradient
|
|
285
|
+
(?:linear|radial|conic)-gradient\( # Supported gradient function names
|
|
286
|
+
/ix
|
|
287
|
+
)
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
# Only left/right floats have an equivalent legacy image alignment.
|
|
291
|
+
def css_float_without_alignment?(css_attr, merged) # :nodoc:
|
|
292
|
+
return false unless css_attr == 'float'
|
|
293
|
+
|
|
294
|
+
!merged[css_attr].match?(/\A\s*(?:left|right)\s*(?:!important\s*)?;?\s*\z/i)
|
|
295
|
+
end
|
|
296
|
+
|
|
274
297
|
def load_css_from_local_file!(path)
|
|
275
298
|
css_block = +''
|
|
276
299
|
path.delete_prefix!('file:')
|
|
@@ -332,7 +355,7 @@ class Premailer
|
|
|
332
355
|
load_css_from_local_file!(link_uri)
|
|
333
356
|
else
|
|
334
357
|
warn "Loading css from uri: " + link_uri if @options[:verbose]
|
|
335
|
-
@css_parser.load_uri!(link_uri, { :only_media_types => [:screen, :handheld] })
|
|
358
|
+
@css_parser.load_uri!(link_uri, { :only_media_types => [:screen, :handheld], :integrity => tag.attributes['integrity']&.to_s })
|
|
336
359
|
end
|
|
337
360
|
|
|
338
361
|
elsif tag.to_s.strip =~ /^<style/i && @options[:include_style_tags]
|
|
@@ -414,7 +437,7 @@ class Premailer
|
|
|
414
437
|
end
|
|
415
438
|
|
|
416
439
|
if href.scheme && (href.scheme != 'http') && (href.scheme != 'https')
|
|
417
|
-
|
|
440
|
+
warn "Skipping append_query_string for: #{href} because scheme is no good" if @options[:verbose]
|
|
418
441
|
next
|
|
419
442
|
end
|
|
420
443
|
|
data/lib/premailer/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: premailer
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.33.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Alex Dunae
|
|
@@ -29,28 +29,14 @@ dependencies:
|
|
|
29
29
|
requirements:
|
|
30
30
|
- - ">="
|
|
31
31
|
- !ruby/object:Gem::Version
|
|
32
|
-
version: 1.
|
|
32
|
+
version: 3.1.0
|
|
33
33
|
type: :runtime
|
|
34
34
|
prerelease: false
|
|
35
35
|
version_requirements: !ruby/object:Gem::Requirement
|
|
36
36
|
requirements:
|
|
37
37
|
- - ">="
|
|
38
38
|
- !ruby/object:Gem::Version
|
|
39
|
-
version: 1.
|
|
40
|
-
- !ruby/object:Gem::Dependency
|
|
41
|
-
name: htmlentities
|
|
42
|
-
requirement: !ruby/object:Gem::Requirement
|
|
43
|
-
requirements:
|
|
44
|
-
- - ">="
|
|
45
|
-
- !ruby/object:Gem::Version
|
|
46
|
-
version: 4.0.0
|
|
47
|
-
type: :runtime
|
|
48
|
-
prerelease: false
|
|
49
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
-
requirements:
|
|
51
|
-
- - ">="
|
|
52
|
-
- !ruby/object:Gem::Version
|
|
53
|
-
version: 4.0.0
|
|
39
|
+
version: 3.1.0
|
|
54
40
|
description: Improve the rendering of HTML emails by making CSS inline, converting
|
|
55
41
|
links and warning about unsupported code.
|
|
56
42
|
email: akzhan.abdulin@gmail.com
|