premailer 1.7.3 → 1.7.8
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +5 -1
- data/.jrubyrc +1 -0
- data/.travis.yml +9 -0
- data/.yardopts +10 -0
- data/Gemfile +12 -1
- data/{LICENSE.rdoc → LICENSE.md} +2 -2
- data/README.md +100 -0
- data/lib/premailer/adapter.rb +14 -10
- data/lib/premailer/adapter/hpricot.rb +22 -16
- data/lib/premailer/adapter/nokogiri.rb +46 -18
- data/lib/premailer/executor.rb +4 -0
- data/lib/premailer/html_to_plain_text.rb +28 -12
- data/lib/premailer/premailer.rb +135 -63
- data/lib/premailer/version.rb +4 -0
- data/premailer.gemspec +14 -5
- data/rakefile.rb +20 -25
- data/test/files/html_with_uri.html +9 -0
- data/test/future_tests.rb +1 -1
- data/test/helper.rb +34 -1
- data/test/test_adapter.rb +1 -1
- data/test/test_html_to_plain_text.rb +25 -2
- data/test/test_links.rb +45 -1
- data/test/test_misc.rb +50 -5
- data/test/test_premailer.rb +60 -39
- data/test/test_warnings.rb +1 -3
- metadata +165 -131
- data/README.rdoc +0 -85
data/lib/premailer/executor.rb
CHANGED
@@ -44,6 +44,10 @@ opts = OptionParser.new do |opts|
|
|
44
44
|
options[:remove_classes] = v
|
45
45
|
end
|
46
46
|
|
47
|
+
opts.on("-j", "--remove-scripts", "Remove <script> elements") do |v|
|
48
|
+
options[:remove_classes] = v
|
49
|
+
end
|
50
|
+
|
47
51
|
opts.on("-l", "--line-length N", Integer, "Line length for plaintext (default: #{options[:line_length].to_s})") do |v|
|
48
52
|
options[:line_length] = v
|
49
53
|
end
|
@@ -13,21 +13,37 @@ module HtmlToPlainText
|
|
13
13
|
# decode HTML entities
|
14
14
|
he = HTMLEntities.new
|
15
15
|
txt = he.decode(txt)
|
16
|
-
|
17
|
-
# replace image by their alt attribute
|
18
|
-
txt.gsub!(/<img.+?alt=\"([^\"]*)\"[^>]*\/>/i, '\1')
|
19
16
|
|
20
|
-
# replace
|
21
|
-
|
22
|
-
|
17
|
+
# replace images with their alt attributes
|
18
|
+
# for img tags with "" for attribute quotes
|
19
|
+
# with or without closing tag
|
20
|
+
# eg. the following formats:
|
21
|
+
# <img alt="" />
|
22
|
+
# <img alt="">
|
23
|
+
txt.gsub!(/<img.+?alt=\"([^\"]*)\"[^>]*\>/i, '\1')
|
24
|
+
|
25
|
+
# for img tags with '' for attribute quotes
|
26
|
+
# with or without closing tag
|
27
|
+
# eg. the following formats:
|
28
|
+
# <img alt='' />
|
29
|
+
# <img alt=''>
|
30
|
+
txt.gsub!(/<img.+?alt=\'([^\']*)\'[^>]*\>/i, '\1')
|
23
31
|
|
24
32
|
# links
|
25
|
-
txt.gsub!(/<a.+?href=\"([^\"]*)\"[^>]*>(
|
26
|
-
|
33
|
+
txt.gsub!(/<a.+?href=\"(mailto:)?([^\"]*)\"[^>]*>((.|\s)*?)<\/a>/i) do |s|
|
34
|
+
if $3.empty?
|
35
|
+
''
|
36
|
+
else
|
37
|
+
$3.strip + ' ( ' + $2.strip + ' )'
|
38
|
+
end
|
27
39
|
end
|
28
40
|
|
29
|
-
txt.gsub!(/<a.+?href='([^\']*)\'[^>]*>(
|
30
|
-
|
41
|
+
txt.gsub!(/<a.+?href='(mailto:)?([^\']*)\'[^>]*>((.|\s)*?)<\/a>/i) do |s|
|
42
|
+
if $3.empty?
|
43
|
+
''
|
44
|
+
else
|
45
|
+
$3.strip + ' ( ' + $2.strip + ' )'
|
46
|
+
end
|
31
47
|
end
|
32
48
|
|
33
49
|
|
@@ -73,7 +89,7 @@ module HtmlToPlainText
|
|
73
89
|
txt.gsub!(/<\/?[^>]*>/, '')
|
74
90
|
|
75
91
|
txt = word_wrap(txt, line_length)
|
76
|
-
|
92
|
+
|
77
93
|
# remove linefeeds (\r\n and \r -> \n)
|
78
94
|
txt.gsub!(/\r\n?/, "\n")
|
79
95
|
|
@@ -87,7 +103,7 @@ module HtmlToPlainText
|
|
87
103
|
|
88
104
|
# no more than two consecutive spaces
|
89
105
|
txt.gsub!(/ {2,}/, " ")
|
90
|
-
|
106
|
+
|
91
107
|
# the word messes up the parens
|
92
108
|
txt.gsub!(/\([ \n](http[^)]+)[\n ]\)/) do |s|
|
93
109
|
"( " + $1 + " )"
|
data/lib/premailer/premailer.rb
CHANGED
@@ -1,47 +1,88 @@
|
|
1
|
-
# Premailer by Alex Dunae (dunae.ca, e-mail 'code' at the same domain), 2008-10
|
2
|
-
#
|
3
1
|
# Premailer processes HTML and CSS to improve e-mail deliverability.
|
4
2
|
#
|
5
3
|
# Premailer's main function is to render all CSS as inline <tt>style</tt>
|
6
4
|
# attributes. It also converts relative links to absolute links and checks
|
7
5
|
# the 'safety' of CSS properties against a CSS support chart.
|
8
6
|
#
|
9
|
-
#
|
10
|
-
#
|
7
|
+
# ## Example of use
|
8
|
+
#
|
9
|
+
# ```ruby
|
10
|
+
# premailer = Premailer.new('http://example.com/myfile.html', :warn_level => Premailer::Warnings::SAFE)
|
11
|
+
#
|
12
|
+
# # Write the HTML output
|
13
|
+
# fout = File.open("output.html", "w")
|
14
|
+
# fout.puts premailer.to_inline_css
|
15
|
+
# fout.close
|
11
16
|
#
|
12
|
-
#
|
13
|
-
#
|
14
|
-
#
|
15
|
-
#
|
17
|
+
# # Write the plain-text output
|
18
|
+
# fout = File.open("ouput.txt", "w")
|
19
|
+
# fout.puts premailer.to_plain_text
|
20
|
+
# fout.close
|
16
21
|
#
|
17
|
-
#
|
18
|
-
#
|
19
|
-
#
|
20
|
-
#
|
22
|
+
# # List any CSS warnings
|
23
|
+
# puts premailer.warnings.length.to_s + ' warnings found'
|
24
|
+
# premailer.warnings.each do |w|
|
25
|
+
# puts "#{w[:message]} (#{w[:level]}) may not render properly in #{w[:clients]}"
|
26
|
+
# end
|
21
27
|
#
|
22
|
-
#
|
23
|
-
#
|
24
|
-
#
|
25
|
-
# puts "#{w[:message]} (#{w[:level]}) may not render properly in #{w[:clients]}"
|
26
|
-
# end
|
28
|
+
# premailer = Premailer.new(html_file, :warn_level => Premailer::Warnings::SAFE)
|
29
|
+
# puts premailer.to_inline_css
|
30
|
+
# ```
|
27
31
|
#
|
28
|
-
|
29
|
-
|
32
|
+
require 'premailer/version'
|
33
|
+
|
30
34
|
class Premailer
|
31
35
|
include HtmlToPlainText
|
32
36
|
include CssParser
|
33
37
|
|
34
|
-
VERSION = '1.7.3'
|
35
|
-
|
36
38
|
CLIENT_SUPPORT_FILE = File.dirname(__FILE__) + '/../../misc/client_support.yaml'
|
37
39
|
|
40
|
+
# Unmergable selectors regexp.
|
38
41
|
RE_UNMERGABLE_SELECTORS = /(\:(visited|active|hover|focus|after|before|selection|target|first\-(line|letter))|^\@)/i
|
42
|
+
# Reset selectors regexp.
|
39
43
|
RE_RESET_SELECTORS = /^(\:\#outlook|body.*|\.ReadMsgBody|\.ExternalClass|img|\#backgroundTable)$/
|
40
44
|
|
45
|
+
# list of HTMLEntities to fix
|
46
|
+
# source: http://stackoverflow.com/questions/2812781/how-to-convert-webpage-apostrophe-8217-to-ascii-39-in-ruby-1-
|
47
|
+
HTML_ENTITIES = {
|
48
|
+
"1.8" => {
|
49
|
+
"\342\200\231" => "'",
|
50
|
+
"\342\200\246" => "...",
|
51
|
+
"\342\200\176" => "'",
|
52
|
+
"\342\200\177" => "'",
|
53
|
+
"\342\200\230" => "'",
|
54
|
+
"\342\200\231" => "'",
|
55
|
+
"\342\200\232" => ',',
|
56
|
+
"\342\200\233" => "'",
|
57
|
+
"\342\200\234" => '"',
|
58
|
+
"\342\200\235" => '"',
|
59
|
+
"\342\200\041" => '-',
|
60
|
+
"\342\200\174" => '-',
|
61
|
+
"\342\200\220" => '-',
|
62
|
+
"\342\200\223" => '-',
|
63
|
+
"\342\200\224" => '--',
|
64
|
+
"\342\200\225" => '--',
|
65
|
+
"\342\200\042" => '--'
|
66
|
+
},
|
67
|
+
"1.9" => {
|
68
|
+
"’" => "'",
|
69
|
+
"…" => "...",
|
70
|
+
"‘" => "'",
|
71
|
+
"‚" => ',',
|
72
|
+
"‛" => "'",
|
73
|
+
"“" => '"',
|
74
|
+
"”" => '"',
|
75
|
+
"‐" => '-',
|
76
|
+
"–" => '-',
|
77
|
+
"—" => '--',
|
78
|
+
"―" => '--'
|
79
|
+
}
|
80
|
+
}
|
81
|
+
|
41
82
|
# list of CSS attributes that can be rendered as HTML attributes
|
42
83
|
#
|
43
|
-
#
|
44
|
-
#
|
84
|
+
# @todo too much repetition
|
85
|
+
# @todo background=""
|
45
86
|
RELATED_ATTRIBUTES = {
|
46
87
|
'h1' => {'text-align' => 'align'},
|
47
88
|
'h2' => {'text-align' => 'align'},
|
@@ -55,6 +96,7 @@ class Premailer
|
|
55
96
|
'body' => {'background-color' => 'bgcolor'},
|
56
97
|
'table' => {
|
57
98
|
'background-color' => 'bgcolor',
|
99
|
+
'background-image' => 'background',
|
58
100
|
'-premailer-width' => 'width',
|
59
101
|
'-premailer-height' => 'height',
|
60
102
|
'-premailer-cellpadding' => 'cellpadding',
|
@@ -77,8 +119,7 @@ class Premailer
|
|
77
119
|
'background-color' => 'bgcolor',
|
78
120
|
'vertical-align' => 'valign',
|
79
121
|
'-premailer-width' => 'width',
|
80
|
-
'-premailer-height' => 'height'
|
81
|
-
'-premailer-colspan' => 'colspan'
|
122
|
+
'-premailer-height' => 'height'
|
82
123
|
},
|
83
124
|
'img' => {'float' => 'align'}
|
84
125
|
}
|
@@ -90,6 +131,7 @@ class Premailer
|
|
90
131
|
attr_reader :base_url
|
91
132
|
|
92
133
|
# base directory used to resolve links for local files
|
134
|
+
# @return [String] base directory
|
93
135
|
attr_reader :base_dir
|
94
136
|
|
95
137
|
# unmergeable CSS rules to be preserved in the head (CssParser)
|
@@ -101,38 +143,49 @@ class Premailer
|
|
101
143
|
# source HTML document (Hpricot/Nokogiri)
|
102
144
|
attr_reader :doc
|
103
145
|
|
146
|
+
# Warning levels
|
104
147
|
module Warnings
|
148
|
+
# No warnings
|
105
149
|
NONE = 0
|
150
|
+
# Safe
|
106
151
|
SAFE = 1
|
152
|
+
# Poor
|
107
153
|
POOR = 2
|
154
|
+
# Risky
|
108
155
|
RISKY = 3
|
109
156
|
end
|
110
157
|
include Warnings
|
111
158
|
|
159
|
+
# Waning level names
|
112
160
|
WARN_LABEL = %w(NONE SAFE POOR RISKY)
|
113
161
|
|
114
162
|
# Create a new Premailer object.
|
115
163
|
#
|
116
|
-
#
|
117
|
-
#
|
118
|
-
#
|
164
|
+
# @param html is the HTML data to process. It can be either an IO object, the URL of a
|
165
|
+
# remote file, a local path or a raw HTML string. If passing an HTML string you
|
166
|
+
# must set the with_html_string option to true.
|
119
167
|
#
|
120
|
-
#
|
121
|
-
# [
|
122
|
-
# [
|
123
|
-
# [
|
124
|
-
# [
|
125
|
-
# [
|
126
|
-
# [
|
127
|
-
# [
|
128
|
-
# [
|
129
|
-
# [
|
130
|
-
# [
|
131
|
-
# [
|
132
|
-
# [
|
133
|
-
# [
|
134
|
-
# [
|
135
|
-
# [
|
168
|
+
# @param [Hash] options the options to handle html with.
|
169
|
+
# @option options [FixNum] :line_length Line length used by to_plain_text. Default is 65.
|
170
|
+
# @option options [FixNum] :warn_level What level of CSS compatibility warnings to show (see {Premailer::Warnings}).
|
171
|
+
# @option options [String] :link_query_string A string to append to every <tt>a href=""</tt> link. Do not include the initial <tt>?</tt>.
|
172
|
+
# @option options [String] :base_url Used to calculate absolute URLs for local files.
|
173
|
+
# @option options [Array(String)] :css Manually specify CSS stylesheets.
|
174
|
+
# @option options [Boolean] :css_to_attributes Copy related CSS attributes into HTML attributes (e.g. background-color to bgcolor)
|
175
|
+
# @option options [String] :css_string Pass CSS as a string
|
176
|
+
# @option options [Boolean] :remove_ids Remove ID attributes whenever possible and convert IDs used as anchors to hashed to avoid collisions in webmail programs. Default is false.
|
177
|
+
# @option options [Boolean] :remove_classes Remove class attributes. Default is false.
|
178
|
+
# @option options [Boolean] :remove_comments Remove html comments. Default is false.
|
179
|
+
# @option options [Boolean] :remove_scripts Remove <tt>script</tt> elements. Default is true.
|
180
|
+
# @option options [Boolean] :preserve_styles Whether to preserve any <tt>link rel=stylesheet</tt> and <tt>style</tt> elements. Default is false.
|
181
|
+
# @option options [Boolean] :preserve_reset Whether to preserve styles associated with the MailChimp reset code.
|
182
|
+
# @option options [Boolean] :with_html_string Whether the html param should be treated as a raw string.
|
183
|
+
# @option options [Boolean] :verbose Whether to print errors and warnings to <tt>$stderr</tt>. Default is false.
|
184
|
+
# @option options [Boolean] :include_link_tags Whether to include css from <tt>link rel=stylesheet</tt> tags. Default is true.
|
185
|
+
# @option options [Boolean] :include_style_tags Whether to include css from <tt>style</tt> tags. Default is true.
|
186
|
+
# @option options [String] :input_encoding Manually specify the source documents encoding. This is a good idea.
|
187
|
+
# @option options [Boolean] :replace_html_entities Convert HTML entities to actual characters. Default is false.
|
188
|
+
# @option options [Symbol] :adapter Which HTML parser to use, either <tt>:nokogiri</tt> or <tt>:hpricot</tt>. Default is <tt>:hpricot</tt>.
|
136
189
|
def initialize(html, options = {})
|
137
190
|
@options = {:warn_level => Warnings::SAFE,
|
138
191
|
:line_length => 65,
|
@@ -141,6 +194,7 @@ class Premailer
|
|
141
194
|
:remove_classes => false,
|
142
195
|
:remove_ids => false,
|
143
196
|
:remove_comments => false,
|
197
|
+
:remove_scripts => true,
|
144
198
|
:css => [],
|
145
199
|
:css_to_attributes => true,
|
146
200
|
:with_html_string => false,
|
@@ -150,7 +204,12 @@ class Premailer
|
|
150
204
|
:verbose => false,
|
151
205
|
:debug => false,
|
152
206
|
:io_exceptions => false,
|
153
|
-
:
|
207
|
+
:include_link_tags => true,
|
208
|
+
:include_style_tags => true,
|
209
|
+
:input_encoding => 'ASCII-8BIT',
|
210
|
+
:replace_html_entities => false,
|
211
|
+
:adapter => Adapter.use,
|
212
|
+
}.merge(options)
|
154
213
|
|
155
214
|
@html_file = html
|
156
215
|
@is_local_file = @options[:with_html_string] || Premailer.local_data?(html)
|
@@ -190,7 +249,8 @@ class Premailer
|
|
190
249
|
load_css_from_html!
|
191
250
|
end
|
192
251
|
|
193
|
-
#
|
252
|
+
# CSS warnings.
|
253
|
+
# @return [Array(Hash)] Array of warnings.
|
194
254
|
def warnings
|
195
255
|
return [] if @options[:warn_level] == Warnings::NONE
|
196
256
|
@css_warnings = check_client_support if @css_warnings.empty?
|
@@ -200,6 +260,7 @@ class Premailer
|
|
200
260
|
protected
|
201
261
|
def load_css_from_local_file!(path)
|
202
262
|
css_block = ''
|
263
|
+
path.gsub!(/\Afile:/, '')
|
203
264
|
begin
|
204
265
|
File.open(path, "r") do |file|
|
205
266
|
while line = file.gets
|
@@ -215,6 +276,7 @@ protected
|
|
215
276
|
@css_parser.add_block!(css_string, {:base_uri => @base_url, :base_dir => @base_dir, :only_media_types => [:screen, :handheld]})
|
216
277
|
end
|
217
278
|
|
279
|
+
# @private
|
218
280
|
def load_css_from_options! # :nodoc:
|
219
281
|
load_css_from_string(@options[:css_string]) if @options[:css_string]
|
220
282
|
|
@@ -229,21 +291,26 @@ protected
|
|
229
291
|
|
230
292
|
# Load CSS included in <tt>style</tt> and <tt>link</tt> tags from an HTML document.
|
231
293
|
def load_css_from_html! # :nodoc:
|
232
|
-
if
|
294
|
+
if (@options[:adapter] == :nokogiri)
|
295
|
+
tags = @doc.search("link[@rel='stylesheet']", "//style[not(contains(@data-premailer,'ignore'))]")
|
296
|
+
else
|
297
|
+
tags = @doc.search("link[@rel='stylesheet'], style:not([@data-premailer='ignore'])")
|
298
|
+
end
|
299
|
+
if tags
|
233
300
|
tags.each do |tag|
|
234
|
-
if tag.to_s.strip =~ /^\<link/i && tag.attributes['href'] && media_type_ok?(tag.attributes['media'])
|
301
|
+
if tag.to_s.strip =~ /^\<link/i && tag.attributes['href'] && media_type_ok?(tag.attributes['media']) && @options[:include_link_tags]
|
235
302
|
# A user might want to <link /> to a local css file that is also mirrored on the site
|
236
303
|
# but the local one is different (e.g. newer) than the live file, premailer will now choose the local file
|
237
|
-
|
304
|
+
|
238
305
|
if tag.attributes['href'].to_s.include? @base_url.to_s and @html_file.kind_of?(String)
|
239
306
|
link_uri = File.join(File.dirname(@html_file), tag.attributes['href'].to_s.sub!(@base_url.to_s, ''))
|
240
307
|
end
|
241
|
-
|
308
|
+
|
242
309
|
# if the file does not exist locally, try to grab the remote reference
|
243
310
|
if link_uri.nil? or not File.exists?(link_uri)
|
244
311
|
link_uri = Premailer.resolve_link(tag.attributes['href'].to_s, @html_file)
|
245
312
|
end
|
246
|
-
|
313
|
+
|
247
314
|
if Premailer.local_data?(link_uri)
|
248
315
|
$stderr.puts "Loading css from local file: " + link_uri if @options[:verbose]
|
249
316
|
load_css_from_local_file!(link_uri)
|
@@ -252,7 +319,7 @@ protected
|
|
252
319
|
@css_parser.load_uri!(link_uri, {:only_media_types => [:screen, :handheld]})
|
253
320
|
end
|
254
321
|
|
255
|
-
elsif tag.to_s.strip =~ /^\<style/i
|
322
|
+
elsif tag.to_s.strip =~ /^\<style/i && @options[:include_style_tags]
|
256
323
|
@css_parser.add_block!(tag.inner_html, :base_uri => @base_url, :base_dir => @base_dir, :only_media_types => [:screen, :handheld])
|
257
324
|
end
|
258
325
|
end
|
@@ -264,6 +331,8 @@ protected
|
|
264
331
|
|
265
332
|
# here be deprecated methods
|
266
333
|
public
|
334
|
+
# @private
|
335
|
+
# @deprecated
|
267
336
|
def local_uri?(uri) # :nodoc:
|
268
337
|
warn "[DEPRECATION] `local_uri?` is deprecated. Please use `Premailer.local_data?` instead."
|
269
338
|
Premailer.local_data?(uri)
|
@@ -271,11 +340,11 @@ public
|
|
271
340
|
|
272
341
|
# here be instance methods
|
273
342
|
|
274
|
-
|
343
|
+
# @private
|
344
|
+
def media_type_ok?(media_types)
|
345
|
+
media_types = media_types.to_s
|
275
346
|
return true if media_types.nil? or media_types.empty?
|
276
347
|
media_types.split(/[\s]+|,/).any? { |media_type| media_type.strip =~ /screen|handheld|all/i }
|
277
|
-
rescue
|
278
|
-
true
|
279
348
|
end
|
280
349
|
|
281
350
|
def append_query_string(doc, qs)
|
@@ -295,7 +364,7 @@ public
|
|
295
364
|
doc.search('a').each do|el|
|
296
365
|
href = el.attributes['href'].to_s.strip
|
297
366
|
next if href.nil? or href.empty?
|
298
|
-
|
367
|
+
|
299
368
|
next if href[0,1] =~ /[\#\{\[\<\%]/ # don't bother with anchors or special-looking links
|
300
369
|
|
301
370
|
begin
|
@@ -327,7 +396,7 @@ public
|
|
327
396
|
doc
|
328
397
|
end
|
329
398
|
|
330
|
-
|
399
|
+
# Check for an XHTML doctype
|
331
400
|
def is_xhtml?
|
332
401
|
intro = @doc.to_html.strip.split("\n")[0..2].join(' ')
|
333
402
|
is_xhtml = !!(intro =~ /w3c\/\/[\s]*dtd[\s]+xhtml/i)
|
@@ -335,7 +404,7 @@ public
|
|
335
404
|
is_xhtml
|
336
405
|
end
|
337
406
|
|
338
|
-
|
407
|
+
# Convert relative links to absolute links.
|
339
408
|
#
|
340
409
|
# Processes <tt>href</tt> <tt>src</tt> and <tt>background</tt> attributes
|
341
410
|
# as well as CSS <tt>url()</tt> declarations found in inline <tt>style</tt> attributes.
|
@@ -356,7 +425,7 @@ public
|
|
356
425
|
tags.each do |tag|
|
357
426
|
# skip links that look like they have merge tags
|
358
427
|
# and mailto, ftp, etc...
|
359
|
-
if tag.attributes[attribute].to_s =~ /^([\%\<\{\#\[]|data:|tel:|file:|sms:|callto:|facetime:|mailto:|ftp:|gopher:)/i
|
428
|
+
if tag.attributes[attribute].to_s =~ /^([\%\<\{\#\[]|data:|tel:|file:|sms:|callto:|facetime:|mailto:|ftp:|gopher:|cid:)/i
|
360
429
|
next
|
361
430
|
end
|
362
431
|
|
@@ -387,20 +456,22 @@ public
|
|
387
456
|
end
|
388
457
|
|
389
458
|
|
459
|
+
# @private
|
390
460
|
def self.escape_string(str) # :nodoc:
|
391
461
|
str.gsub(/"/ , "'")
|
392
462
|
end
|
393
463
|
|
464
|
+
# @private
|
394
465
|
def self.resolve_link(path, base_path) # :nodoc:
|
395
466
|
path.strip!
|
396
467
|
resolved = nil
|
397
|
-
if path =~
|
468
|
+
if path =~ /\A(?:(https?|ftp|file):)\/\//i
|
398
469
|
resolved = path
|
399
470
|
Premailer.canonicalize(resolved)
|
400
471
|
elsif base_path.kind_of?(URI)
|
401
472
|
resolved = base_path.merge(path)
|
402
473
|
Premailer.canonicalize(resolved)
|
403
|
-
elsif base_path.kind_of?(String) and base_path =~
|
474
|
+
elsif base_path.kind_of?(String) and base_path =~ /\A(?:(?:https?|ftp|file):)\/\//i
|
404
475
|
resolved = URI.parse(base_path)
|
405
476
|
resolved = resolved.merge(path)
|
406
477
|
Premailer.canonicalize(resolved)
|
@@ -413,8 +484,9 @@ public
|
|
413
484
|
#
|
414
485
|
# IO objects return true, as do strings that look like URLs.
|
415
486
|
def self.local_data?(data)
|
416
|
-
return true
|
417
|
-
return
|
487
|
+
return true if data.is_a?(IO) || data.is_a?(StringIO)
|
488
|
+
return true if data =~ /\Afile:\/\//i
|
489
|
+
return false if data =~ /\A(?:(https?|ftp):)\/\//i
|
418
490
|
true
|
419
491
|
end
|
420
492
|
|