premailer 1.27.0 → 1.28.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 +69 -0
- data/lib/premailer/adapter/nokogiri.rb +25 -27
- data/lib/premailer/adapter/nokogiri_fast.rb +35 -39
- data/lib/premailer/adapter/nokogumbo.rb +27 -31
- data/lib/premailer/adapter/rgb_to_hex.rb +3 -3
- data/lib/premailer/adapter.rb +7 -11
- data/lib/premailer/executor.rb +8 -8
- data/lib/premailer/html_to_plain_text.rb +16 -14
- data/lib/premailer/premailer.rb +142 -110
- data/lib/premailer/version.rb +1 -1
- metadata +12 -147
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7e9ea60eb04b53f20269417902391f03464d1dd263f98cb0884b65e1d019a9e5
|
|
4
|
+
data.tar.gz: 448daeae92d362f8ec092300793d729bed26622590f98122c27e379ab24b631f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 57c3c0f79e925c133c999c683e683bc1c7a3b0889baaa184b0bc39d41c869d677e269eb371b3231c20607bb267ff5797ea7b323a80d57db3d62266cd964e603b
|
|
7
|
+
data.tar.gz: fb2bb6a1f120102a6f6f4e54543fb315257f69bf8f1564dc3ba10d7fcbcc29bf8ff8247e38fd7bd3abbb6beeb38e899dc5e0015e858c371e1742c292cd3b8b92
|
data/README.md
CHANGED
|
@@ -127,6 +127,75 @@ Premailer.new(
|
|
|
127
127
|
|
|
128
128
|
[available options](https://premailer.github.io/premailer/Premailer.html#initialize-instance_method)
|
|
129
129
|
|
|
130
|
+
## Support for CSS variables
|
|
131
|
+
|
|
132
|
+
The gem does not automatically replace CSS variables with their static values.
|
|
133
|
+
|
|
134
|
+
For example, if a variable is used to set the `font-weight` of an `h1` element, the result will be
|
|
135
|
+
```html
|
|
136
|
+
<h1 style="
|
|
137
|
+
font-size:3rem;
|
|
138
|
+
font-weight:var(--bulma-content-heading-weight);">
|
|
139
|
+
Title</h1>
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
This causes the `font-weight` value to be the CSS variable call `var(--bulma-content-heading-weight);` instead of its static value.
|
|
143
|
+
|
|
144
|
+
### Replace CSS variable calls with their static values
|
|
145
|
+
|
|
146
|
+
The following section instructs how to replace CSS variables with their static value in the context of a Ruby on Rails application.
|
|
147
|
+
|
|
148
|
+
Install the `postcss-css-variables` plugin for PostCSS to process the CSS variables.
|
|
149
|
+
|
|
150
|
+
```shell
|
|
151
|
+
yarn add postcss postcss-cli postcss-css-variables
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
To configure the plugin, create the file `postcss.config.js` in the root directory with the content:
|
|
155
|
+
|
|
156
|
+
```javascript
|
|
157
|
+
module.exports = {
|
|
158
|
+
plugins: [
|
|
159
|
+
// https://github.com/MadLittleMods/postcss-css-variables to transform the css
|
|
160
|
+
require("postcss-css-variables")({
|
|
161
|
+
preserve: false, // Set to false to replace variables with static values
|
|
162
|
+
}),
|
|
163
|
+
],
|
|
164
|
+
};
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
In the `package.json` file, add the new "build:emails" to the scripts.<br>Replace `./app/assets/stylesheets/emails.css` with your file path:
|
|
168
|
+
```json
|
|
169
|
+
"scripts": {
|
|
170
|
+
"build:emails": "postcss ./app/assets/stylesheets/emails.css -o ./app/assets/builds/emails.css"
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
The previous script processes and overwrites the file at `./app/assets/stylesheets/emails.css` with PostCSS using its `postcss-css-variables` plugin, replacing the CSS variables with their static value.
|
|
175
|
+
|
|
176
|
+
If the file to be processed is not `.css`, but `.scss`, it needs to be converted first to `.css`, then have its variables replaced. The script would then be
|
|
177
|
+
|
|
178
|
+
```json
|
|
179
|
+
"scripts": {
|
|
180
|
+
"build:emails": "sass ./app/assets/stylesheets/emails.scss:./app/assets/builds/emails.css --no-source-map --load-path=node_modules && postcss ./app/assets/builds/emails.css -o ./app/assets/builds/emails.css"
|
|
181
|
+
}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Next, to execute the script when running `bin/dev`, add the following line in the file `Procfile.dev`
|
|
185
|
+
|
|
186
|
+
```
|
|
187
|
+
emails_css: yarn build:emails --watch
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
The srcipt can also be executed separately with the command
|
|
191
|
+
|
|
192
|
+
```shell
|
|
193
|
+
yarn build:emails
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### Caveat
|
|
197
|
+
|
|
198
|
+
The variables must be declared before use. Otherwise, their values when called will be set to `undefined`.
|
|
130
199
|
|
|
131
200
|
## Contributions
|
|
132
201
|
|
|
@@ -23,19 +23,19 @@ class Premailer
|
|
|
23
23
|
# Iterate through the rules and merge them into the HTML
|
|
24
24
|
@css_parser.each_selector(:all) do |selector, declaration, specificity, media_types|
|
|
25
25
|
# Save un-mergable rules separately
|
|
26
|
-
selector.gsub!(/:link([\s]*)+/i) { |
|
|
26
|
+
selector.gsub!(/:link([\s]*)+/i) { |_m| $1 }
|
|
27
27
|
|
|
28
28
|
# Convert element names to lower case
|
|
29
|
-
selector.gsub!(/([\s]|^)([\w]+)/) { |
|
|
29
|
+
selector.gsub!(/([\s]|^)([\w]+)/) { |_m| $1.to_s + $2.to_s.downcase }
|
|
30
30
|
|
|
31
|
-
if Premailer.
|
|
31
|
+
if Premailer.media_query?(media_types) || selector =~ Premailer::RE_UNMERGABLE_SELECTORS
|
|
32
32
|
@unmergable_rules.add_rule_set!(CssParser::RuleSet.new(selectors: selector, block: declaration), media_types) unless @options[:preserve_styles]
|
|
33
33
|
else
|
|
34
34
|
begin
|
|
35
|
-
if Premailer::RE_RESET_SELECTORS.match?(selector)
|
|
35
|
+
if Premailer::RE_RESET_SELECTORS.match?(selector) && !!@options[:preserve_reset]
|
|
36
36
|
# this is in place to preserve the MailChimp CSS reset: http://github.com/mailchimp/Email-Blueprints/
|
|
37
37
|
# however, this doesn't mean for testing pur
|
|
38
|
-
@unmergable_rules.add_rule_set!(CssParser::RuleSet.new(selectors: selector, block: declaration))
|
|
38
|
+
@unmergable_rules.add_rule_set!(CssParser::RuleSet.new(selectors: selector, block: declaration))
|
|
39
39
|
end
|
|
40
40
|
|
|
41
41
|
# Change single ID CSS selectors into xpath so that we can match more
|
|
@@ -43,14 +43,14 @@ class Premailer
|
|
|
43
43
|
selector.gsub!(/\A\#([\w_\-]+)\Z/, '*[@id=\1]')
|
|
44
44
|
|
|
45
45
|
doc.search(selector).each do |el|
|
|
46
|
-
if el.elem?
|
|
46
|
+
if el.elem? && ((el.name != 'head') && (el.parent.name != 'head'))
|
|
47
47
|
# Add a style attribute or append to the existing one
|
|
48
48
|
block = "[SPEC=#{specificity}[#{declaration}]]"
|
|
49
49
|
el['style'] = (el.attributes['style'].to_s ||= '') + ' ' + block
|
|
50
50
|
end
|
|
51
51
|
end
|
|
52
52
|
rescue ::Nokogiri::SyntaxError, RuntimeError, ArgumentError
|
|
53
|
-
|
|
53
|
+
warn "CSS syntax error with selector: #{selector}" if @options[:verbose]
|
|
54
54
|
next
|
|
55
55
|
end
|
|
56
56
|
end
|
|
@@ -63,7 +63,7 @@ class Premailer
|
|
|
63
63
|
doc.search("*[@style]").each do |el|
|
|
64
64
|
style = el.attributes['style'].to_s
|
|
65
65
|
|
|
66
|
-
declarations = style.scan(/\[SPEC
|
|
66
|
+
declarations = style.scan(/\[SPEC=([\d]+)\[(.[^\]]*)\]\]/m).filter_map do |declaration|
|
|
67
67
|
rs = Premailer::CachedRuleSet.new(block: declaration[1].to_s, specificity: declaration[0].to_i)
|
|
68
68
|
rs.expand_shorthand!
|
|
69
69
|
rs
|
|
@@ -80,9 +80,9 @@ class Premailer
|
|
|
80
80
|
end
|
|
81
81
|
|
|
82
82
|
# Duplicate CSS attributes as HTML attributes
|
|
83
|
-
if Premailer::RELATED_ATTRIBUTES.
|
|
83
|
+
if Premailer::RELATED_ATTRIBUTES.key?(el.name) && @options[:css_to_attributes]
|
|
84
84
|
Premailer::RELATED_ATTRIBUTES[el.name].each do |css_attr, html_attr|
|
|
85
|
-
if el[html_attr].nil?
|
|
85
|
+
if el[html_attr].nil? && !merged[css_attr].empty?
|
|
86
86
|
new_val = merged[css_attr].dup
|
|
87
87
|
|
|
88
88
|
# Remove url() function wrapper
|
|
@@ -117,9 +117,9 @@ class Premailer
|
|
|
117
117
|
|
|
118
118
|
doc = write_unmergable_css_rules(doc, @unmergable_rules) unless @options[:drop_unmergeable_css_rules]
|
|
119
119
|
|
|
120
|
-
if @options[:remove_classes]
|
|
120
|
+
if @options[:remove_classes] || @options[:remove_comments]
|
|
121
121
|
doc.traverse do |el|
|
|
122
|
-
if el.comment?
|
|
122
|
+
if el.comment? && @options[:remove_comments]
|
|
123
123
|
el.remove
|
|
124
124
|
elsif el.element?
|
|
125
125
|
el.remove_attribute('class') if @options[:remove_classes]
|
|
@@ -131,7 +131,7 @@ class Premailer
|
|
|
131
131
|
# find all anchor's targets and hash them
|
|
132
132
|
targets = []
|
|
133
133
|
doc.search("a[@href^='#']").each do |el|
|
|
134
|
-
target = el.get_attribute('href')[1
|
|
134
|
+
target = el.get_attribute('href')[1..]
|
|
135
135
|
targets << target
|
|
136
136
|
el.set_attribute('href', "#" + Digest::SHA256.hexdigest(target))
|
|
137
137
|
end
|
|
@@ -153,7 +153,7 @@ class Premailer
|
|
|
153
153
|
end
|
|
154
154
|
|
|
155
155
|
@processed_doc = doc
|
|
156
|
-
if
|
|
156
|
+
if xhtml?
|
|
157
157
|
# we don't want to encode carriage returns
|
|
158
158
|
@processed_doc.to_xhtml(:encoding => @options[:output_encoding]).gsub(/&\#(xD|13);/i, "\r")
|
|
159
159
|
else
|
|
@@ -175,17 +175,16 @@ class Premailer
|
|
|
175
175
|
style_tag.content = styles
|
|
176
176
|
doc.add_child(style_tag)
|
|
177
177
|
else
|
|
178
|
-
style_tag = doc.create_element "style",
|
|
178
|
+
style_tag = doc.create_element "style", styles.to_s
|
|
179
179
|
head = doc.at_css('head')
|
|
180
|
-
head ||= doc.root.first_element_child.add_previous_sibling(doc.create_element
|
|
181
|
-
head ||= doc.add_child(doc.create_element
|
|
180
|
+
head ||= doc.root.first_element_child.add_previous_sibling(doc.create_element("head")) if doc.root&.first_element_child
|
|
181
|
+
head ||= doc.add_child(doc.create_element("head"))
|
|
182
182
|
head << style_tag
|
|
183
183
|
end
|
|
184
184
|
end
|
|
185
185
|
doc
|
|
186
186
|
end
|
|
187
187
|
|
|
188
|
-
|
|
189
188
|
# Converts the HTML document to a format suitable for plain-text e-mail.
|
|
190
189
|
#
|
|
191
190
|
# If present, uses the <body> element as its base; otherwise uses the whole document.
|
|
@@ -195,17 +194,17 @@ class Premailer
|
|
|
195
194
|
html_src = ''
|
|
196
195
|
begin
|
|
197
196
|
html_src = @doc.at("body").inner_html
|
|
198
|
-
rescue
|
|
197
|
+
rescue StandardError
|
|
199
198
|
end
|
|
200
199
|
|
|
201
|
-
html_src = @doc.to_html unless html_src
|
|
200
|
+
html_src = @doc.to_html unless html_src && !html_src.empty?
|
|
202
201
|
convert_to_text(html_src, @options[:line_length], @html_encoding)
|
|
203
202
|
end
|
|
204
203
|
|
|
205
204
|
# Gets the original HTML as a string.
|
|
206
205
|
# @return [String] HTML.
|
|
207
206
|
def to_s
|
|
208
|
-
if
|
|
207
|
+
if xhtml?
|
|
209
208
|
@doc.to_xhtml(:encoding => nil)
|
|
210
209
|
else
|
|
211
210
|
@doc.to_html(:encoding => nil)
|
|
@@ -219,13 +218,13 @@ class Premailer
|
|
|
219
218
|
thing = nil
|
|
220
219
|
|
|
221
220
|
# TODO: duplicate options
|
|
222
|
-
if @options[:with_html_string]
|
|
221
|
+
if @options[:with_html_string] || @options[:inline] || input.respond_to?(:read)
|
|
223
222
|
thing = input
|
|
224
223
|
elsif @is_local_file
|
|
225
224
|
@base_dir = File.dirname(input)
|
|
226
225
|
thing = File.open(input, 'r')
|
|
227
226
|
else
|
|
228
|
-
thing = URI.
|
|
227
|
+
thing = URI.parse(input).open
|
|
229
228
|
end
|
|
230
229
|
|
|
231
230
|
if thing.respond_to?(:read)
|
|
@@ -236,7 +235,7 @@ class Premailer
|
|
|
236
235
|
doc = nil
|
|
237
236
|
|
|
238
237
|
# Handle HTML entities
|
|
239
|
-
if @options[:replace_html_entities] == true
|
|
238
|
+
if (@options[:replace_html_entities] == true) && thing.is_a?(String)
|
|
240
239
|
thing = +thing
|
|
241
240
|
HTML_ENTITIES.map do |entity, replacement|
|
|
242
241
|
thing.gsub! entity, replacement
|
|
@@ -246,7 +245,7 @@ class Premailer
|
|
|
246
245
|
doc = if @options[:html_fragment]
|
|
247
246
|
::Nokogiri::HTML.fragment(thing, encoding)
|
|
248
247
|
else
|
|
249
|
-
::Nokogiri::HTML(thing, nil, encoding
|
|
248
|
+
::Nokogiri::HTML(thing, nil, encoding, &:recover)
|
|
250
249
|
end
|
|
251
250
|
|
|
252
251
|
# Fix for removing any CDATA tags from both style and script tags inserted per
|
|
@@ -254,13 +253,12 @@ class Premailer
|
|
|
254
253
|
# https://github.com/premailer/premailer/issues/199
|
|
255
254
|
['style', 'script'].each do |tag|
|
|
256
255
|
doc.search(tag).children.each do |child|
|
|
257
|
-
child.swap(child.text
|
|
256
|
+
child.swap(child.text) if child.cdata?
|
|
258
257
|
end
|
|
259
258
|
end
|
|
260
259
|
|
|
261
260
|
doc
|
|
262
261
|
end
|
|
263
|
-
|
|
264
262
|
end
|
|
265
263
|
end
|
|
266
264
|
end
|
|
@@ -27,34 +27,33 @@ class Premailer
|
|
|
27
27
|
|
|
28
28
|
# Iterate through the rules and merge them into the HTML
|
|
29
29
|
@css_parser.each_selector(:all) do |selector, declaration, specificity, media_types|
|
|
30
|
-
|
|
31
30
|
# Save un-mergable rules separately
|
|
32
|
-
selector.gsub!(/:link([\s]*)+/i) { |
|
|
31
|
+
selector.gsub!(/:link([\s]*)+/i) { |_m| $1 }
|
|
33
32
|
|
|
34
33
|
# Convert element names to lower case
|
|
35
|
-
selector.gsub!(/([\s]|^)([\w]+)/) { |
|
|
34
|
+
selector.gsub!(/([\s]|^)([\w]+)/) { |_m| $1.to_s + $2.to_s.downcase }
|
|
36
35
|
|
|
37
|
-
if Premailer.
|
|
36
|
+
if Premailer.media_query?(media_types) || selector =~ Premailer::RE_UNMERGABLE_SELECTORS
|
|
38
37
|
@unmergable_rules.add_rule_set!(CssParser::RuleSet.new(selectors: selector, block: declaration), media_types) unless @options[:preserve_styles]
|
|
39
38
|
else
|
|
40
39
|
begin
|
|
41
|
-
if Premailer::RE_RESET_SELECTORS.match?(selector)
|
|
40
|
+
if Premailer::RE_RESET_SELECTORS.match?(selector) && !!@options[:preserve_reset]
|
|
42
41
|
# this is in place to preserve the MailChimp CSS reset: http://github.com/mailchimp/Email-Blueprints/
|
|
43
42
|
# however, this doesn't mean for testing pur
|
|
44
|
-
@unmergable_rules.add_rule_set!(CssParser::RuleSet.new(selectors: selector, block: declaration))
|
|
43
|
+
@unmergable_rules.add_rule_set!(CssParser::RuleSet.new(selectors: selector, block: declaration))
|
|
45
44
|
end
|
|
46
45
|
|
|
47
46
|
# Try the new index based technique. If not supported, fall back to the old brute force one.
|
|
48
47
|
nodes = match_selector(index, all_nodes, descendants, selector) || doc.search(selector)
|
|
49
48
|
nodes.each do |el|
|
|
50
|
-
if el.elem?
|
|
49
|
+
if el.elem? && ((el.name != 'head') && (el.parent.name != 'head'))
|
|
51
50
|
# Add a style attribute or append to the existing one
|
|
52
51
|
block = "[SPEC=#{specificity}[#{declaration}]]"
|
|
53
52
|
el['style'] = (el.attributes['style'].to_s ||= '') + ' ' + block
|
|
54
53
|
end
|
|
55
54
|
end
|
|
56
55
|
rescue ::Nokogiri::SyntaxError, RuntimeError, ArgumentError
|
|
57
|
-
|
|
56
|
+
warn "CSS syntax error with selector: #{selector}" if @options[:verbose]
|
|
58
57
|
next
|
|
59
58
|
end
|
|
60
59
|
end
|
|
@@ -68,13 +67,11 @@ class Premailer
|
|
|
68
67
|
style = el.attributes['style'].to_s
|
|
69
68
|
|
|
70
69
|
declarations = []
|
|
71
|
-
style.scan(/\[SPEC
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
raise e if @options[:rule_set_exceptions]
|
|
77
|
-
end
|
|
70
|
+
style.scan(/\[SPEC=([\d]+)\[(.[^\]]*)\]\]/m).each do |declaration|
|
|
71
|
+
rs = CssParser::RuleSet.new(block: declaration[1].to_s, specificity: declaration[0].to_i)
|
|
72
|
+
declarations << rs
|
|
73
|
+
rescue ArgumentError => e
|
|
74
|
+
raise e if @options[:rule_set_exceptions]
|
|
78
75
|
end
|
|
79
76
|
|
|
80
77
|
# Perform style folding
|
|
@@ -86,9 +83,9 @@ class Premailer
|
|
|
86
83
|
end
|
|
87
84
|
|
|
88
85
|
# Duplicate CSS attributes as HTML attributes
|
|
89
|
-
if Premailer::RELATED_ATTRIBUTES.
|
|
86
|
+
if Premailer::RELATED_ATTRIBUTES.key?(el.name) && @options[:css_to_attributes]
|
|
90
87
|
Premailer::RELATED_ATTRIBUTES[el.name].each do |css_attr, html_attr|
|
|
91
|
-
if el[html_attr].nil?
|
|
88
|
+
if el[html_attr].nil? && !merged[css_attr].empty?
|
|
92
89
|
new_val = merged[css_attr].dup
|
|
93
90
|
|
|
94
91
|
# Remove url() function wrapper
|
|
@@ -123,9 +120,9 @@ class Premailer
|
|
|
123
120
|
|
|
124
121
|
doc = write_unmergable_css_rules(doc, @unmergable_rules) unless @options[:drop_unmergeable_css_rules]
|
|
125
122
|
|
|
126
|
-
if @options[:remove_classes]
|
|
123
|
+
if @options[:remove_classes] || @options[:remove_comments]
|
|
127
124
|
doc.traverse do |el|
|
|
128
|
-
if el.comment?
|
|
125
|
+
if el.comment? && @options[:remove_comments]
|
|
129
126
|
el.remove
|
|
130
127
|
elsif el.element?
|
|
131
128
|
el.remove_attribute('class') if @options[:remove_classes]
|
|
@@ -137,7 +134,7 @@ class Premailer
|
|
|
137
134
|
# find all anchor's targets and hash them
|
|
138
135
|
targets = []
|
|
139
136
|
doc.search("a[@href^='#']").each do |el|
|
|
140
|
-
target = el.get_attribute('href')[1
|
|
137
|
+
target = el.get_attribute('href')[1..]
|
|
141
138
|
targets << target
|
|
142
139
|
el.set_attribute('href', "#" + Digest::SHA256.hexdigest(target))
|
|
143
140
|
end
|
|
@@ -159,7 +156,7 @@ class Premailer
|
|
|
159
156
|
end
|
|
160
157
|
|
|
161
158
|
@processed_doc = doc
|
|
162
|
-
if
|
|
159
|
+
if xhtml?
|
|
163
160
|
# we don't want to encode carriage returns
|
|
164
161
|
@processed_doc.to_xhtml(:encoding => @options[:output_encoding]).gsub(/&\#(xD|13);/i, "\r")
|
|
165
162
|
else
|
|
@@ -183,15 +180,14 @@ class Premailer
|
|
|
183
180
|
else
|
|
184
181
|
style_tag = doc.create_element "style", styles
|
|
185
182
|
head = doc.at_css('head')
|
|
186
|
-
head ||= doc.root.first_element_child.add_previous_sibling(doc.create_element
|
|
187
|
-
head ||= doc.add_child(doc.create_element
|
|
183
|
+
head ||= doc.root.first_element_child.add_previous_sibling(doc.create_element("head")) if doc.root&.first_element_child
|
|
184
|
+
head ||= doc.add_child(doc.create_element("head"))
|
|
188
185
|
head << style_tag
|
|
189
186
|
end
|
|
190
187
|
end
|
|
191
188
|
doc
|
|
192
189
|
end
|
|
193
190
|
|
|
194
|
-
|
|
195
191
|
# Converts the HTML document to a format suitable for plain-text e-mail.
|
|
196
192
|
#
|
|
197
193
|
# If present, uses the <body> element as its base; otherwise uses the whole document.
|
|
@@ -201,17 +197,17 @@ class Premailer
|
|
|
201
197
|
html_src = ''
|
|
202
198
|
begin
|
|
203
199
|
html_src = @doc.at("body").inner_html
|
|
204
|
-
rescue
|
|
200
|
+
rescue StandardError
|
|
205
201
|
end
|
|
206
202
|
|
|
207
|
-
html_src = @doc.to_html unless html_src
|
|
203
|
+
html_src = @doc.to_html unless html_src && !html_src.empty?
|
|
208
204
|
convert_to_text(html_src, @options[:line_length], @html_encoding)
|
|
209
205
|
end
|
|
210
206
|
|
|
211
207
|
# Gets the original HTML as a string.
|
|
212
208
|
# @return [String] HTML.
|
|
213
209
|
def to_s
|
|
214
|
-
if
|
|
210
|
+
if xhtml?
|
|
215
211
|
@doc.to_xhtml(:encoding => nil)
|
|
216
212
|
else
|
|
217
213
|
@doc.to_html(:encoding => nil)
|
|
@@ -225,13 +221,13 @@ class Premailer
|
|
|
225
221
|
thing = nil
|
|
226
222
|
|
|
227
223
|
# TODO: duplicate options
|
|
228
|
-
if @options[:with_html_string]
|
|
224
|
+
if @options[:with_html_string] || @options[:inline] || input.respond_to?(:read)
|
|
229
225
|
thing = input
|
|
230
226
|
elsif @is_local_file
|
|
231
227
|
@base_dir = File.dirname(input)
|
|
232
228
|
thing = File.open(input, 'r')
|
|
233
229
|
else
|
|
234
|
-
thing = URI.
|
|
230
|
+
thing = URI.parse(input).open
|
|
235
231
|
end
|
|
236
232
|
|
|
237
233
|
if thing.respond_to?(:read)
|
|
@@ -242,7 +238,7 @@ class Premailer
|
|
|
242
238
|
doc = nil
|
|
243
239
|
|
|
244
240
|
# Handle HTML entities
|
|
245
|
-
if @options[:replace_html_entities] == true
|
|
241
|
+
if (@options[:replace_html_entities] == true) && thing.is_a?(String)
|
|
246
242
|
HTML_ENTITIES.map do |entity, replacement|
|
|
247
243
|
thing.gsub! entity, replacement
|
|
248
244
|
end
|
|
@@ -251,7 +247,7 @@ class Premailer
|
|
|
251
247
|
doc = if @options[:html_fragment]
|
|
252
248
|
::Nokogiri::HTML.fragment(thing, encoding)
|
|
253
249
|
else
|
|
254
|
-
::Nokogiri::HTML(thing, nil, encoding
|
|
250
|
+
::Nokogiri::HTML(thing, nil, encoding, &:recover)
|
|
255
251
|
end
|
|
256
252
|
|
|
257
253
|
# Fix for removing any CDATA tags from both style and script tags inserted per
|
|
@@ -259,7 +255,7 @@ class Premailer
|
|
|
259
255
|
# https://github.com/premailer/premailer/issues/199
|
|
260
256
|
['style', 'script'].each do |tag|
|
|
261
257
|
doc.search(tag).children.each do |child|
|
|
262
|
-
child.swap(child.text
|
|
258
|
+
child.swap(child.text) if child.cdata?
|
|
263
259
|
end
|
|
264
260
|
end
|
|
265
261
|
|
|
@@ -283,7 +279,7 @@ class Premailer
|
|
|
283
279
|
page.traverse do |node|
|
|
284
280
|
all_nodes.push(node)
|
|
285
281
|
|
|
286
|
-
if node != page
|
|
282
|
+
if node != page
|
|
287
283
|
index_ancestry(page, node, node.parent, descendants)
|
|
288
284
|
end
|
|
289
285
|
|
|
@@ -294,7 +290,7 @@ class Premailer
|
|
|
294
290
|
# Index the node by all class attributes it possesses.
|
|
295
291
|
# Classes are modestly selective. Usually more than tag names
|
|
296
292
|
# but less selective than ids.
|
|
297
|
-
if node.has_attribute?("class")
|
|
293
|
+
if node.has_attribute?("class")
|
|
298
294
|
node.get_attribute("class").split(/\s+/).each do |c|
|
|
299
295
|
c = '.' + c
|
|
300
296
|
index[c] = (index[c] || Set.new).add(node)
|
|
@@ -303,7 +299,7 @@ class Premailer
|
|
|
303
299
|
|
|
304
300
|
# Index the node by its "id" attribute if it has one.
|
|
305
301
|
# This is usually the most selective of the three.
|
|
306
|
-
if node.has_attribute?("id")
|
|
302
|
+
if node.has_attribute?("id")
|
|
307
303
|
id = '#' + node.get_attribute("id")
|
|
308
304
|
index[id] = (index[id] || Set.new).add(node)
|
|
309
305
|
end
|
|
@@ -325,9 +321,9 @@ class Premailer
|
|
|
325
321
|
# @param descendants The running hash map of node -> set of nodes that maps descendants of a node.
|
|
326
322
|
# @return The descendants argument after updating it.
|
|
327
323
|
def index_ancestry(doc, elem, parent, descendants)
|
|
328
|
-
if parent
|
|
324
|
+
if parent
|
|
329
325
|
descendants[parent] = (descendants[parent] || Set.new).add(elem)
|
|
330
|
-
if doc != parent
|
|
326
|
+
if doc != parent
|
|
331
327
|
index_ancestry(doc, elem, parent.parent, descendants)
|
|
332
328
|
end
|
|
333
329
|
end
|
|
@@ -356,14 +352,14 @@ class Premailer
|
|
|
356
352
|
# It will return nil when such a selector is passed, so you can take
|
|
357
353
|
# action on the falsity of the return value.
|
|
358
354
|
def match_selector(index, all_nodes, descendants, selector)
|
|
359
|
-
if /[^-a-zA-Z0-9_\s.#]/.match?(selector)
|
|
355
|
+
if /[^-a-zA-Z0-9_\s.#]/.match?(selector)
|
|
360
356
|
return nil
|
|
361
357
|
end
|
|
362
358
|
|
|
363
359
|
take_children = false
|
|
364
360
|
selector.split(/\s+/).reduce(all_nodes) do |base, spec|
|
|
365
361
|
desc = base
|
|
366
|
-
if take_children
|
|
362
|
+
if take_children
|
|
367
363
|
desc = Set.new
|
|
368
364
|
base.each do |n|
|
|
369
365
|
desc.merge(descendants[n])
|