premailer 1.12.0 → 1.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,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.
@@ -25,34 +27,33 @@ class Premailer
25
27
 
26
28
  # Iterate through the rules and merge them into the HTML
27
29
  @css_parser.each_selector(:all) do |selector, declaration, specificity, media_types|
28
-
29
30
  # Save un-mergable rules separately
30
- selector.gsub!(/:link([\s]*)+/i) { |m| $1 }
31
+ selector.gsub!(/:link([\s]*)+/i) { |_m| $1 }
31
32
 
32
33
  # Convert element names to lower case
33
- selector.gsub!(/([\s]|^)([\w]+)/) { |m| $1.to_s + $2.to_s.downcase }
34
+ selector.gsub!(/([\s]|^)([\w]+)/) { |_m| $1.to_s + $2.to_s.downcase }
34
35
 
35
- 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]
36
+ if Premailer.media_query?(media_types) || selector =~ Premailer::RE_UNMERGABLE_SELECTORS
37
+ @unmergable_rules.add_rule_set!(CssParser::RuleSet.new(selectors: selector, block: declaration), media_types) unless @options[:preserve_styles]
37
38
  else
38
39
  begin
39
- if selector =~ Premailer::RE_RESET_SELECTORS
40
+ if Premailer::RE_RESET_SELECTORS.match?(selector) && !!@options[:preserve_reset]
40
41
  # this is in place to preserve the MailChimp CSS reset: http://github.com/mailchimp/Email-Blueprints/
41
42
  # however, this doesn't mean for testing pur
42
- @unmergable_rules.add_rule_set!(CssParser::RuleSet.new(selector, declaration)) unless !@options[:preserve_reset]
43
+ @unmergable_rules.add_rule_set!(CssParser::RuleSet.new(selectors: selector, block: declaration))
43
44
  end
44
45
 
45
46
  # Try the new index based technique. If not supported, fall back to the old brute force one.
46
47
  nodes = match_selector(index, all_nodes, descendants, selector) || doc.search(selector)
47
48
  nodes.each do |el|
48
- if el.elem? and (el.name != 'head' and el.parent.name != 'head')
49
+ if el.elem? && ((el.name != 'head') && (el.parent.name != 'head'))
49
50
  # Add a style attribute or append to the existing one
50
51
  block = "[SPEC=#{specificity}[#{declaration}]]"
51
52
  el['style'] = (el.attributes['style'].to_s ||= '') + ' ' + block
52
53
  end
53
54
  end
54
55
  rescue ::Nokogiri::SyntaxError, RuntimeError, ArgumentError
55
- $stderr.puts "CSS syntax error with selector: #{selector}" if @options[:verbose]
56
+ warn "CSS syntax error with selector: #{selector}" if @options[:verbose]
56
57
  next
57
58
  end
58
59
  end
@@ -66,29 +67,50 @@ class Premailer
66
67
  style = el.attributes['style'].to_s
67
68
 
68
69
  declarations = []
69
- style.scan(/\[SPEC\=([\d]+)\[(.[^\]\]]*)\]\]/).each do |declaration|
70
- rs = CssParser::RuleSet.new(nil, declaration[1].to_s, declaration[0].to_i)
70
+ style.scan(/\[SPEC=([\d]+)\[(.[^\]]*)\]\]/m).each do |declaration|
71
+ rs = CssParser::RuleSet.new(block: declaration[1].to_s, specificity: declaration[0].to_i)
71
72
  declarations << rs
73
+ rescue ArgumentError => e
74
+ raise e if @options[:rule_set_exceptions]
72
75
  end
73
76
 
74
77
  # Perform style folding
75
78
  merged = CssParser.merge(declarations)
76
- merged.expand_shorthand!
79
+ begin
80
+ merged.expand_shorthand!
81
+ rescue ArgumentError => e
82
+ raise e if @options[:rule_set_exceptions]
83
+ end
77
84
 
78
85
  # Duplicate CSS attributes as HTML attributes
79
- if Premailer::RELATED_ATTRIBUTES.has_key?(el.name) && @options[:css_to_attributes]
80
- Premailer::RELATED_ATTRIBUTES[el.name].each do |css_att, html_att|
81
- if el[html_att].nil? and not merged[css_att].empty?
82
- new_html_att = merged[css_att].gsub(/url\(['"](.*)['"]\)/, '\1').gsub(/;$|\s*!important/, '').strip
83
- el[html_att] = css_att.end_with?('color') && @options[:rgb_to_hex_attributes] ? ensure_hex(new_html_att) : new_html_att
86
+ if Premailer::RELATED_ATTRIBUTES.key?(el.name) && @options[:css_to_attributes]
87
+ Premailer::RELATED_ATTRIBUTES[el.name].each do |css_attr, html_attr|
88
+ if el[html_attr].nil? && !merged[css_attr].empty?
89
+ new_val = merged[css_attr].dup
90
+
91
+ # Remove url() function wrapper
92
+ new_val.gsub!(/url\((['"])(.*?)\1\)/, '\2')
93
+
94
+ # Remove !important, trailing semi-colon, and leading/trailing whitespace
95
+ new_val.gsub!(/;$|\s*!important/, '').strip!
96
+
97
+ # For width and height tags, remove px units
98
+ new_val.gsub!(/(\d+)px/, '\1') if WIDTH_AND_HEIGHT.include?(html_attr)
99
+
100
+ # For color-related tags, convert RGB to hex if specified by options
101
+ new_val = ensure_hex(new_val) if css_attr.end_with?('color') && @options[:rgb_to_hex_attributes]
102
+
103
+ el[html_attr] = new_val
84
104
  end
105
+
85
106
  unless @options[:preserve_style_attribute]
86
- merged.instance_variable_get("@declarations").tap do |declarations|
87
- declarations.delete(css_att)
107
+ merged.instance_variable_get(:@declarations).tap do |declarations|
108
+ declarations.delete(css_attr)
88
109
  end
89
110
  end
90
111
  end
91
112
  end
113
+
92
114
  # Collapse multiple rules into one as much as possible.
93
115
  merged.create_shorthand! if @options[:create_shorthands]
94
116
 
@@ -98,9 +120,9 @@ class Premailer
98
120
 
99
121
  doc = write_unmergable_css_rules(doc, @unmergable_rules) unless @options[:drop_unmergeable_css_rules]
100
122
 
101
- if @options[:remove_classes] or @options[:remove_comments]
123
+ if @options[:remove_classes] || @options[:remove_comments]
102
124
  doc.traverse do |el|
103
- if el.comment? and @options[:remove_comments]
125
+ if el.comment? && @options[:remove_comments]
104
126
  el.remove
105
127
  elsif el.element?
106
128
  el.remove_attribute('class') if @options[:remove_classes]
@@ -112,15 +134,15 @@ class Premailer
112
134
  # find all anchor's targets and hash them
113
135
  targets = []
114
136
  doc.search("a[@href^='#']").each do |el|
115
- target = el.get_attribute('href')[1..-1]
137
+ target = el.get_attribute('href')[1..]
116
138
  targets << target
117
- el.set_attribute('href', "#" + Digest::MD5.hexdigest(target))
139
+ el.set_attribute('href', "#" + Digest::SHA256.hexdigest(target))
118
140
  end
119
141
  # hash ids that are links target, delete others
120
142
  doc.search("*[@id]").each do |el|
121
143
  id = el.get_attribute('id')
122
144
  if targets.include?(id)
123
- el.set_attribute('id', Digest::MD5.hexdigest(id))
145
+ el.set_attribute('id', Digest::SHA256.hexdigest(id))
124
146
  else
125
147
  el.remove_attribute('id')
126
148
  end
@@ -134,7 +156,7 @@ class Premailer
134
156
  end
135
157
 
136
158
  @processed_doc = doc
137
- if is_xhtml?
159
+ if xhtml?
138
160
  # we don't want to encode carriage returns
139
161
  @processed_doc.to_xhtml(:encoding => @options[:output_encoding]).gsub(/&\#(xD|13);/i, "\r")
140
162
  else
@@ -158,15 +180,14 @@ class Premailer
158
180
  else
159
181
  style_tag = doc.create_element "style", styles
160
182
  head = doc.at_css('head')
161
- head ||= doc.root.first_element_child.add_previous_sibling(doc.create_element "head") if doc.root && doc.root.first_element_child
162
- head ||= doc.add_child(doc.create_element "head")
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"))
163
185
  head << style_tag
164
186
  end
165
187
  end
166
188
  doc
167
189
  end
168
190
 
169
-
170
191
  # Converts the HTML document to a format suitable for plain-text e-mail.
171
192
  #
172
193
  # If present, uses the <body> element as its base; otherwise uses the whole document.
@@ -176,17 +197,17 @@ class Premailer
176
197
  html_src = ''
177
198
  begin
178
199
  html_src = @doc.at("body").inner_html
179
- rescue;
200
+ rescue StandardError
180
201
  end
181
202
 
182
- html_src = @doc.to_html unless html_src and not html_src.empty?
203
+ html_src = @doc.to_html unless html_src && !html_src.empty?
183
204
  convert_to_text(html_src, @options[:line_length], @html_encoding)
184
205
  end
185
206
 
186
207
  # Gets the original HTML as a string.
187
208
  # @return [String] HTML.
188
209
  def to_s
189
- if is_xhtml?
210
+ if xhtml?
190
211
  @doc.to_xhtml(:encoding => nil)
191
212
  else
192
213
  @doc.to_html(:encoding => nil)
@@ -200,13 +221,13 @@ class Premailer
200
221
  thing = nil
201
222
 
202
223
  # TODO: duplicate options
203
- if @options[:with_html_string] or @options[:inline] or input.respond_to?(:read)
224
+ if @options[:with_html_string] || @options[:inline] || input.respond_to?(:read)
204
225
  thing = input
205
226
  elsif @is_local_file
206
227
  @base_dir = File.dirname(input)
207
228
  thing = File.open(input, 'r')
208
229
  else
209
- thing = open(input)
230
+ thing = URI.parse(input).open
210
231
  end
211
232
 
212
233
  if thing.respond_to?(:read)
@@ -217,31 +238,24 @@ class Premailer
217
238
  doc = nil
218
239
 
219
240
  # Handle HTML entities
220
- if @options[:replace_html_entities] == true and thing.is_a?(String)
241
+ if (@options[:replace_html_entities] == true) && thing.is_a?(String)
221
242
  HTML_ENTITIES.map do |entity, replacement|
222
243
  thing.gsub! entity, replacement
223
244
  end
224
245
  end
225
- # Default encoding is ASCII-8BIT (binary) per http://groups.google.com/group/nokogiri-talk/msg/0b81ef0dc180dc74
226
- # However, we really don't want to hardcode this. ASCII-8BIT should be the default, but not the only option.
227
- encoding = if thing.is_a?(String) and RUBY_VERSION =~ /1.9/
228
- thing = thing.force_encoding(@options[:input_encoding]).encode!
229
- @options[:input_encoding]
230
- else
231
- @options[:input_encoding] || (RUBY_PLATFORM == 'java' ? nil : 'BINARY')
232
- end
246
+ encoding = @options[:input_encoding] || (RUBY_PLATFORM == 'java' ? nil : 'BINARY')
233
247
  doc = if @options[:html_fragment]
234
248
  ::Nokogiri::HTML.fragment(thing, encoding)
235
249
  else
236
- ::Nokogiri::HTML(thing, nil, encoding) { |c| c.recover }
250
+ ::Nokogiri::HTML(thing, nil, encoding, &:recover)
237
251
  end
238
252
 
239
253
  # Fix for removing any CDATA tags from both style and script tags inserted per
240
254
  # https://github.com/sparklemotion/nokogiri/issues/311 and
241
255
  # https://github.com/premailer/premailer/issues/199
242
- %w(style script).each do |tag|
256
+ ['style', 'script'].each do |tag|
243
257
  doc.search(tag).children.each do |child|
244
- child.swap(child.text()) if child.cdata?
258
+ child.swap(child.text) if child.cdata?
245
259
  end
246
260
  end
247
261
 
@@ -265,7 +279,7 @@ class Premailer
265
279
  page.traverse do |node|
266
280
  all_nodes.push(node)
267
281
 
268
- if node != page then
282
+ if node != page
269
283
  index_ancestry(page, node, node.parent, descendants)
270
284
  end
271
285
 
@@ -276,7 +290,7 @@ class Premailer
276
290
  # Index the node by all class attributes it possesses.
277
291
  # Classes are modestly selective. Usually more than tag names
278
292
  # but less selective than ids.
279
- if node.has_attribute?("class") then
293
+ if node.has_attribute?("class")
280
294
  node.get_attribute("class").split(/\s+/).each do |c|
281
295
  c = '.' + c
282
296
  index[c] = (index[c] || Set.new).add(node)
@@ -285,7 +299,7 @@ class Premailer
285
299
 
286
300
  # Index the node by its "id" attribute if it has one.
287
301
  # This is usually the most selective of the three.
288
- if node.has_attribute?("id") then
302
+ if node.has_attribute?("id")
289
303
  id = '#' + node.get_attribute("id")
290
304
  index[id] = (index[id] || Set.new).add(node)
291
305
  end
@@ -298,7 +312,7 @@ class Premailer
298
312
  index.default = Set.new
299
313
  descendants.default = Set.new
300
314
 
301
- return index, Set.new(all_nodes), descendants
315
+ [index, Set.new(all_nodes), descendants]
302
316
  end
303
317
 
304
318
  # @param doc The top level document
@@ -307,9 +321,9 @@ class Premailer
307
321
  # @param descendants The running hash map of node -> set of nodes that maps descendants of a node.
308
322
  # @return The descendants argument after updating it.
309
323
  def index_ancestry(doc, elem, parent, descendants)
310
- if parent then
324
+ if parent
311
325
  descendants[parent] = (descendants[parent] || Set.new).add(elem)
312
- if doc != parent then
326
+ if doc != parent
313
327
  index_ancestry(doc, elem, parent.parent, descendants)
314
328
  end
315
329
  end
@@ -338,14 +352,14 @@ class Premailer
338
352
  # It will return nil when such a selector is passed, so you can take
339
353
  # action on the falsity of the return value.
340
354
  def match_selector(index, all_nodes, descendants, selector)
341
- if /[^-a-zA-Z0-9_\s.#]/.match(selector) then
355
+ if /[^-a-zA-Z0-9_\s.#]/.match?(selector)
342
356
  return nil
343
357
  end
344
358
 
345
359
  take_children = false
346
360
  selector.split(/\s+/).reduce(all_nodes) do |base, spec|
347
361
  desc = base
348
- if take_children then
362
+ if take_children
349
363
  desc = Set.new
350
364
  base.each do |n|
351
365
  desc.merge(descendants[n])
@@ -1,9 +1,9 @@
1
- require 'nokogumbo'
2
-
1
+ # frozen_string_literal: true
3
2
  class Premailer
4
3
  module Adapter
5
4
  # Nokogiri adapter
6
5
  module Nokogumbo
6
+ WIDTH_AND_HEIGHT = ['width', 'height'].freeze
7
7
 
8
8
  include AdapterHelper::RgbToHex
9
9
  # Merge CSS into the HTML document.
@@ -21,19 +21,19 @@ class Premailer
21
21
  # Iterate through the rules and merge them into the HTML
22
22
  @css_parser.each_selector(:all) do |selector, declaration, specificity, media_types|
23
23
  # Save un-mergable rules separately
24
- selector.gsub!(/:link([\s]*)+/i) { |m| $1 }
24
+ selector.gsub!(/:link([\s]*)+/i) { |_m| $1 }
25
25
 
26
26
  # Convert element names to lower case
27
- selector.gsub!(/([\s]|^)([\w]+)/) { |m| $1.to_s + $2.to_s.downcase }
27
+ selector.gsub!(/([\s]|^)([\w]+)/) { |_m| $1.to_s + $2.to_s.downcase }
28
28
 
29
- 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]
29
+ if Premailer.media_query?(media_types) || selector =~ Premailer::RE_UNMERGABLE_SELECTORS
30
+ @unmergable_rules.add_rule_set!(CssParser::RuleSet.new(selectors: selector, block: declaration), media_types) unless @options[:preserve_styles]
31
31
  else
32
32
  begin
33
- if selector =~ Premailer::RE_RESET_SELECTORS
33
+ if Premailer::RE_RESET_SELECTORS.match?(selector) && !!@options[:preserve_reset]
34
34
  # this is in place to preserve the MailChimp CSS reset: http://github.com/mailchimp/Email-Blueprints/
35
35
  # however, this doesn't mean for testing pur
36
- @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))
37
37
  end
38
38
 
39
39
  # Change single ID CSS selectors into xpath so that we can match more
@@ -41,52 +41,71 @@ class Premailer
41
41
  selector.gsub!(/\A\#([\w_\-]+)\Z/, '*[@id=\1]')
42
42
 
43
43
  doc.search(selector).each do |el|
44
- if el.elem? and (el.name != 'head' and el.parent.name != 'head')
44
+ if el.elem? && ((el.name != 'head') && (el.parent.name != 'head'))
45
45
  # Add a style attribute or append to the existing one
46
46
  block = "[SPEC=#{specificity}[#{declaration}]]"
47
47
  el['style'] = (el.attributes['style'].to_s ||= '') + ' ' + block
48
48
  end
49
49
  end
50
50
  rescue ::Nokogiri::SyntaxError, RuntimeError, ArgumentError
51
- $stderr.puts "CSS syntax error with selector: #{selector}" if @options[:verbose]
51
+ warn "CSS syntax error with selector: #{selector}" if @options[:verbose]
52
52
  next
53
53
  end
54
54
  end
55
55
  end
56
56
 
57
57
  # Remove script tags
58
- if @options[:remove_scripts]
59
- doc.search("script").remove
60
- end
58
+ doc.search("script").remove if @options[:remove_scripts]
61
59
 
62
60
  # Read STYLE attributes and perform folding
63
61
  doc.search("*[@style]").each do |el|
64
62
  style = el.attributes['style'].to_s
65
63
 
66
64
  declarations = []
67
- style.scan(/\[SPEC\=([\d]+)\[(.[^\]\]]*)\]\]/).each do |declaration|
68
- rs = CssParser::RuleSet.new(nil, declaration[1].to_s, declaration[0].to_i)
65
+ style.scan(/\[SPEC=([\d]+)\[(.[^\]]*)\]\]/m).each do |declaration|
66
+ rs = CssParser::RuleSet.new(block: declaration[1].to_s, specificity: declaration[0].to_i)
69
67
  declarations << rs
68
+ rescue ArgumentError => e
69
+ raise e if @options[:rule_set_exceptions]
70
70
  end
71
71
 
72
72
  # Perform style folding
73
73
  merged = CssParser.merge(declarations)
74
- merged.expand_shorthand!
74
+ begin
75
+ merged.expand_shorthand!
76
+ rescue ArgumentError => e
77
+ raise e if @options[:rule_set_exceptions]
78
+ end
75
79
 
76
80
  # Duplicate CSS attributes as HTML attributes
77
- if Premailer::RELATED_ATTRIBUTES.has_key?(el.name) && @options[:css_to_attributes]
78
- Premailer::RELATED_ATTRIBUTES[el.name].each do |css_att, html_att|
79
- if el[html_att].nil? and not merged[css_att].empty?
80
- new_html_att = merged[css_att].gsub(/url\(['"](.*)['"]\)/, '\1').gsub(/;$|\s*!important/, '').strip
81
- el[html_att] = css_att.end_with?('color') && @options[:rgb_to_hex_attributes] ? ensure_hex(new_html_att) : new_html_att
81
+ if Premailer::RELATED_ATTRIBUTES.key?(el.name) && @options[:css_to_attributes]
82
+ Premailer::RELATED_ATTRIBUTES[el.name].each do |css_attr, html_attr|
83
+ if el[html_attr].nil? && !merged[css_attr].empty?
84
+ new_val = merged[css_attr].dup
85
+
86
+ # Remove url() function wrapper
87
+ new_val.gsub!(/url\((['"])(.*?)\1\)/, '\2')
88
+
89
+ # Remove !important, trailing semi-colon, and leading/trailing whitespace
90
+ new_val.gsub!(/;$|\s*!important/, '').strip!
91
+
92
+ # For width and height tags, remove px units
93
+ new_val.gsub!(/(\d+)px/, '\1') if WIDTH_AND_HEIGHT.include?(html_attr)
94
+
95
+ # For color-related tags, convert RGB to hex if specified by options
96
+ new_val = ensure_hex(new_val) if css_attr.end_with?('color') && @options[:rgb_to_hex_attributes]
97
+
98
+ el[html_attr] = new_val
82
99
  end
100
+
83
101
  unless @options[:preserve_style_attribute]
84
- merged.instance_variable_get("@declarations").tap do |declarations|
85
- declarations.delete(css_att)
102
+ merged.instance_variable_get(:@declarations).tap do |declarations|
103
+ declarations.delete(css_attr)
86
104
  end
87
105
  end
88
106
  end
89
107
  end
108
+
90
109
  # Collapse multiple rules into one as much as possible.
91
110
  merged.create_shorthand! if @options[:create_shorthands]
92
111
 
@@ -96,9 +115,9 @@ class Premailer
96
115
 
97
116
  doc = write_unmergable_css_rules(doc, @unmergable_rules) unless @options[:drop_unmergeable_css_rules]
98
117
 
99
- if @options[:remove_classes] or @options[:remove_comments]
118
+ if @options[:remove_classes] || @options[:remove_comments]
100
119
  doc.traverse do |el|
101
- if el.comment? and @options[:remove_comments]
120
+ if el.comment? && @options[:remove_comments]
102
121
  el.remove
103
122
  elsif el.element?
104
123
  el.remove_attribute('class') if @options[:remove_classes]
@@ -110,15 +129,15 @@ class Premailer
110
129
  # find all anchor's targets and hash them
111
130
  targets = []
112
131
  doc.search("a[@href^='#']").each do |el|
113
- target = el.get_attribute('href')[1..-1]
132
+ target = el.get_attribute('href')[1..]
114
133
  targets << target
115
- el.set_attribute('href', "#" + Digest::MD5.hexdigest(target))
134
+ el.set_attribute('href', "#" + Digest::SHA256.hexdigest(target))
116
135
  end
117
136
  # hash ids that are links target, delete others
118
137
  doc.search("*[@id]").each do |el|
119
138
  id = el.get_attribute('id')
120
139
  if targets.include?(id)
121
- el.set_attribute('id', Digest::MD5.hexdigest(id))
140
+ el.set_attribute('id', Digest::SHA256.hexdigest(id))
122
141
  else
123
142
  el.remove_attribute('id')
124
143
  end
@@ -132,7 +151,7 @@ class Premailer
132
151
  end
133
152
 
134
153
  @processed_doc = doc
135
- if is_xhtml?
154
+ if xhtml?
136
155
  # we don't want to encode carriage returns
137
156
  @processed_doc.to_xhtml(:encoding => @options[:output_encoding]).gsub(/&\#(xD|13);/i, "\r")
138
157
  else
@@ -156,15 +175,14 @@ class Premailer
156
175
  else
157
176
  style_tag = doc.create_element "style", styles
158
177
  head = doc.at_css('head')
159
- head ||= doc.root.first_element_child.add_previous_sibling(doc.create_element "head") if doc.root && doc.root.first_element_child
160
- head ||= doc.add_child(doc.create_element "head")
178
+ head ||= doc.root.first_element_child.add_previous_sibling(doc.create_element("head")) if doc.root&.first_element_child
179
+ head ||= doc.add_child(doc.create_element("head"))
161
180
  head << style_tag
162
181
  end
163
182
  end
164
183
  doc
165
184
  end
166
185
 
167
-
168
186
  # Converts the HTML document to a format suitable for plain-text e-mail.
169
187
  #
170
188
  # If present, uses the <body> element as its base; otherwise uses the whole document.
@@ -174,17 +192,17 @@ class Premailer
174
192
  html_src = ''
175
193
  begin
176
194
  html_src = @doc.at("body").inner_html
177
- rescue;
195
+ rescue StandardError
178
196
  end
179
197
 
180
- html_src = @doc.to_html unless html_src and not html_src.empty?
198
+ html_src = @doc.to_html unless html_src && !html_src.empty?
181
199
  convert_to_text(html_src, @options[:line_length], @html_encoding)
182
200
  end
183
201
 
184
202
  # Gets the original HTML as a string.
185
203
  # @return [String] HTML.
186
204
  def to_s
187
- if is_xhtml?
205
+ if xhtml?
188
206
  @doc.to_xhtml(:encoding => nil)
189
207
  else
190
208
  @doc.to_html(:encoding => nil)
@@ -198,13 +216,13 @@ class Premailer
198
216
  thing = nil
199
217
 
200
218
  # TODO: duplicate options
201
- if @options[:with_html_string] or @options[:inline] or input.respond_to?(:read)
219
+ if @options[:with_html_string] || @options[:inline] || input.respond_to?(:read)
202
220
  thing = input
203
221
  elsif @is_local_file
204
222
  @base_dir = File.dirname(input)
205
223
  thing = File.open(input, 'r')
206
224
  else
207
- thing = open(input)
225
+ thing = URI.parse(input).open
208
226
  end
209
227
 
210
228
  if thing.respond_to?(:read)
@@ -215,16 +233,11 @@ class Premailer
215
233
  doc = nil
216
234
 
217
235
  # Handle HTML entities
218
- if @options[:replace_html_entities] == true and thing.is_a?(String)
236
+ if (@options[:replace_html_entities] == true) && thing.is_a?(String)
219
237
  HTML_ENTITIES.map do |entity, replacement|
220
238
  thing.gsub! entity, replacement
221
239
  end
222
240
  end
223
- # Default encoding is ASCII-8BIT (binary) per http://groups.google.com/group/nokogiri-talk/msg/0b81ef0dc180dc74
224
- # However, we really don't want to hardcode this. ASCII-8BIT should be the default, but not the only option.
225
- if thing.is_a?(String) and RUBY_VERSION =~ /1.9/
226
- thing = thing.force_encoding(@options[:input_encoding]).encode!
227
- end
228
241
  doc = if @options[:html_fragment]
229
242
  ::Nokogiri::HTML5.fragment(thing)
230
243
  else
@@ -234,15 +247,14 @@ class Premailer
234
247
  # Fix for removing any CDATA tags from both style and script tags inserted per
235
248
  # https://github.com/sparklemotion/nokogiri/issues/311 and
236
249
  # https://github.com/premailer/premailer/issues/199
237
- %w(style script).each do |tag|
250
+ ['style', 'script'].each do |tag|
238
251
  doc.search(tag).children.each do |child|
239
- child.swap(child.text()) if child.cdata?
252
+ child.swap(child.text) if child.cdata?
240
253
  end
241
254
  end
242
255
 
243
256
  doc
244
257
  end
245
-
246
258
  end
247
259
  end
248
260
  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
@@ -6,23 +7,24 @@ module AdapterHelper
6
7
  str.to_i.to_s(16).rjust(2, '0').upcase
7
8
  end
8
9
 
9
- def is_rgb?(color)
10
+ def 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)
22
24
  end
23
25
 
24
26
  def ensure_hex(color)
25
- match_data = is_rgb?(color)
27
+ match_data = rgb?(color)
26
28
  if match_data
27
29
  "#{to_hex(match_data[1])}#{to_hex(match_data[2])}#{to_hex(match_data[3])}"
28
30
  else
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  class Premailer
2
3
  # Manages the adapter classes. Currently supports:
3
4
  #
@@ -5,7 +6,6 @@ class Premailer
5
6
  # * nokogiri_fast
6
7
  # * nokogumbo
7
8
  module Adapter
8
-
9
9
  autoload :Nokogiri, 'premailer/adapter/nokogiri'
10
10
  autoload :NokogiriFast, 'premailer/adapter/nokogiri_fast'
11
11
  autoload :Nokogumbo, 'premailer/adapter/nokogumbo'
@@ -14,13 +14,13 @@ class Premailer
14
14
  REQUIREMENT_MAP = [
15
15
  ["nokogiri", :nokogiri],
16
16
  ["nokogiri", :nokogiri_fast],
17
- ["nokogumbo", :nokogumbo],
18
- ]
17
+ ["nokogumbo", :nokogumbo]
18
+ ].freeze
19
19
 
20
20
  # Returns the adapter to use.
21
21
  def self.use
22
22
  return @use if @use
23
- self.use = self.default
23
+ self.use = default
24
24
  @use
25
25
  end
26
26
 
@@ -34,15 +34,13 @@ class Premailer
34
34
  return :nokogumbo if defined?(::Nokogumbo)
35
35
 
36
36
  REQUIREMENT_MAP.each do |(library, adapter)|
37
- begin
38
- require library
39
- return adapter
40
- rescue LoadError
41
- next
42
- end
37
+ require library
38
+ return adapter
39
+ rescue LoadError
40
+ next
43
41
  end
44
42
 
45
- raise RuntimeError.new("No suitable adapter for Premailer was found, please install nokogiri or nokogumbo")
43
+ raise "No suitable adapter for Premailer was found, please install nokogiri or nokogumbo"
46
44
  end
47
45
 
48
46
  # Sets the adapter to use.
@@ -56,10 +54,9 @@ class Premailer
56
54
  def self.find(adapter)
57
55
  return adapter if adapter.is_a?(Module)
58
56
 
59
- Premailer::Adapter.const_get("#{adapter.to_s.split('_').map{|s| s.capitalize}.join('')}")
57
+ Premailer::Adapter.const_get(adapter.to_s.split('_').map(&:capitalize).join.to_s)
60
58
  rescue NameError
61
59
  raise ArgumentError, "Invalid adapter: #{adapter}"
62
60
  end
63
-
64
61
  end
65
62
  end