premailer 1.7.1 → 1.7.3
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +7 -0
- data/Gemfile +3 -0
- data/LICENSE.rdoc +11 -0
- data/README.rdoc +20 -0
- data/bin/premailer +3 -95
- data/lib/premailer.rb +5 -3
- data/lib/premailer/adapter/hpricot.rb +21 -11
- data/lib/premailer/adapter/nokogiri.rb +18 -17
- data/lib/premailer/executor.rb +96 -0
- data/lib/premailer/html_to_plain_text.rb +26 -6
- data/lib/premailer/premailer.rb +62 -32
- data/local-premailer +9 -0
- data/premailer.gemspec +22 -0
- data/rakefile.rb +69 -0
- data/test/files/base.html +142 -0
- data/test/files/chars.html +6 -0
- data/test/files/contact_bg.png +0 -0
- data/test/files/dialect.png +0 -0
- data/test/files/dots_end.png +0 -0
- data/test/files/dots_h.gif +0 -0
- data/test/files/html4.html +12 -0
- data/test/files/import.css +13 -0
- data/test/files/inc/2009-placeholder.png +0 -0
- data/test/files/iso-8859-2.html +1 -0
- data/test/files/iso-8859-5.html +8 -0
- data/test/files/no_css.html +11 -0
- data/test/files/noimport.css +13 -0
- data/test/files/styles.css +106 -0
- data/test/files/xhtml.html +11 -0
- data/test/future_tests.rb +50 -0
- data/test/helper.rb +7 -0
- data/test/test_adapter.rb +29 -0
- data/test/test_html_to_plain_text.rb +149 -0
- data/test/test_links.rb +141 -0
- data/test/test_misc.rb +233 -0
- data/test/test_premailer.rb +257 -0
- data/test/test_warnings.rb +97 -0
- metadata +109 -13
data/lib/premailer/premailer.rb
CHANGED
@@ -1,5 +1,3 @@
|
|
1
|
-
#!/usr/bin/ruby
|
2
|
-
#
|
3
1
|
# Premailer by Alex Dunae (dunae.ca, e-mail 'code' at the same domain), 2008-10
|
4
2
|
#
|
5
3
|
# Premailer processes HTML and CSS to improve e-mail deliverability.
|
@@ -33,11 +31,12 @@ class Premailer
|
|
33
31
|
include HtmlToPlainText
|
34
32
|
include CssParser
|
35
33
|
|
36
|
-
VERSION = '1.7.
|
34
|
+
VERSION = '1.7.3'
|
37
35
|
|
38
36
|
CLIENT_SUPPORT_FILE = File.dirname(__FILE__) + '/../../misc/client_support.yaml'
|
39
37
|
|
40
38
|
RE_UNMERGABLE_SELECTORS = /(\:(visited|active|hover|focus|after|before|selection|target|first\-(line|letter))|^\@)/i
|
39
|
+
RE_RESET_SELECTORS = /^(\:\#outlook|body.*|\.ReadMsgBody|\.ExternalClass|img|\#backgroundTable)$/
|
41
40
|
|
42
41
|
# list of CSS attributes that can be rendered as HTML attributes
|
43
42
|
#
|
@@ -54,10 +53,33 @@ class Premailer
|
|
54
53
|
'div' => {'text-align' => 'align'},
|
55
54
|
'blockquote' => {'text-align' => 'align'},
|
56
55
|
'body' => {'background-color' => 'bgcolor'},
|
57
|
-
'table' => {
|
58
|
-
|
59
|
-
|
60
|
-
|
56
|
+
'table' => {
|
57
|
+
'background-color' => 'bgcolor',
|
58
|
+
'-premailer-width' => 'width',
|
59
|
+
'-premailer-height' => 'height',
|
60
|
+
'-premailer-cellpadding' => 'cellpadding',
|
61
|
+
'-premailer-cellspacing' => 'cellspacing',
|
62
|
+
},
|
63
|
+
'tr' => {
|
64
|
+
'text-align' => 'align',
|
65
|
+
'background-color' => 'bgcolor',
|
66
|
+
'-premailer-height' => 'height'
|
67
|
+
},
|
68
|
+
'th' => {
|
69
|
+
'text-align' => 'align',
|
70
|
+
'background-color' => 'bgcolor',
|
71
|
+
'vertical-align' => 'valign',
|
72
|
+
'-premailer-width' => 'width',
|
73
|
+
'-premailer-height' => 'height'
|
74
|
+
},
|
75
|
+
'td' => {
|
76
|
+
'text-align' => 'align',
|
77
|
+
'background-color' => 'bgcolor',
|
78
|
+
'vertical-align' => 'valign',
|
79
|
+
'-premailer-width' => 'width',
|
80
|
+
'-premailer-height' => 'height',
|
81
|
+
'-premailer-colspan' => 'colspan'
|
82
|
+
},
|
61
83
|
'img' => {'float' => 'align'}
|
62
84
|
}
|
63
85
|
|
@@ -100,13 +122,14 @@ class Premailer
|
|
100
122
|
# [+warn_level+] What level of CSS compatibility warnings to show (see Warnings).
|
101
123
|
# [+link_query_string+] A string to append to every <tt>a href=""</tt> link. Do not include the initial <tt>?</tt>.
|
102
124
|
# [+base_url+] Used to calculate absolute URLs for local files.
|
103
|
-
# [+css+] Manually specify
|
125
|
+
# [+css+] Manually specify CSS stylesheets.
|
104
126
|
# [+css_to_attributes+] Copy related CSS attributes into HTML attributes (e.g. +background-color+ to +bgcolor+)
|
105
127
|
# [+css_string+] Pass CSS as a string
|
106
128
|
# [+remove_ids+] Remove ID attributes whenever possible and convert IDs used as anchors to hashed to avoid collisions in webmail programs. Default is +false+.
|
107
129
|
# [+remove_classes+] Remove class attributes. Default is +false+.
|
108
130
|
# [+remove_comments+] Remove html comments. Default is +false+.
|
109
131
|
# [+preserve_styles+] Whether to preserve any <tt>link rel=stylesheet</tt> and <tt>style</tt> elements. Default is +false+.
|
132
|
+
# [+preserve_reset+] Whether to preserve styles associated with the MailChimp reset code
|
110
133
|
# [+with_html_string+] Whether the +html+ param should be treated as a raw string.
|
111
134
|
# [+verbose+] Whether to print errors and warnings to <tt>$stderr</tt>. Default is +false+.
|
112
135
|
# [+adapter+] Which HTML parser to use, either <tt>:nokogiri</tt> or <tt>:hpricot</tt>. Default is <tt>:hpricot</tt>.
|
@@ -123,6 +146,7 @@ class Premailer
|
|
123
146
|
:with_html_string => false,
|
124
147
|
:css_string => nil,
|
125
148
|
:preserve_styles => false,
|
149
|
+
:preserve_reset => true,
|
126
150
|
:verbose => false,
|
127
151
|
:debug => false,
|
128
152
|
:io_exceptions => false,
|
@@ -131,7 +155,7 @@ class Premailer
|
|
131
155
|
@html_file = html
|
132
156
|
@is_local_file = @options[:with_html_string] || Premailer.local_data?(html)
|
133
157
|
|
134
|
-
@css_files = @options[:css]
|
158
|
+
@css_files = [@options[:css]].flatten
|
135
159
|
|
136
160
|
@css_warnings = []
|
137
161
|
|
@@ -207,9 +231,19 @@ protected
|
|
207
231
|
def load_css_from_html! # :nodoc:
|
208
232
|
if tags = @doc.search("link[@rel='stylesheet'], style")
|
209
233
|
tags.each do |tag|
|
210
|
-
if tag.to_s.strip =~ /^\<link/i
|
211
|
-
|
212
|
-
|
234
|
+
if tag.to_s.strip =~ /^\<link/i && tag.attributes['href'] && media_type_ok?(tag.attributes['media'])
|
235
|
+
# A user might want to <link /> to a local css file that is also mirrored on the site
|
236
|
+
# but the local one is different (e.g. newer) than the live file, premailer will now choose the local file
|
237
|
+
|
238
|
+
if tag.attributes['href'].to_s.include? @base_url.to_s and @html_file.kind_of?(String)
|
239
|
+
link_uri = File.join(File.dirname(@html_file), tag.attributes['href'].to_s.sub!(@base_url.to_s, ''))
|
240
|
+
end
|
241
|
+
|
242
|
+
# if the file does not exist locally, try to grab the remote reference
|
243
|
+
if link_uri.nil? or not File.exists?(link_uri)
|
244
|
+
link_uri = Premailer.resolve_link(tag.attributes['href'].to_s, @html_file)
|
245
|
+
end
|
246
|
+
|
213
247
|
if Premailer.local_data?(link_uri)
|
214
248
|
$stderr.puts "Loading css from local file: " + link_uri if @options[:verbose]
|
215
249
|
load_css_from_local_file!(link_uri)
|
@@ -239,9 +273,9 @@ public
|
|
239
273
|
|
240
274
|
def media_type_ok?(media_types) # :nodoc:
|
241
275
|
return true if media_types.nil? or media_types.empty?
|
242
|
-
|
276
|
+
media_types.split(/[\s]+|,/).any? { |media_type| media_type.strip =~ /screen|handheld|all/i }
|
243
277
|
rescue
|
244
|
-
|
278
|
+
true
|
245
279
|
end
|
246
280
|
|
247
281
|
def append_query_string(doc, qs)
|
@@ -261,7 +295,8 @@ public
|
|
261
295
|
doc.search('a').each do|el|
|
262
296
|
href = el.attributes['href'].to_s.strip
|
263
297
|
next if href.nil? or href.empty?
|
264
|
-
|
298
|
+
|
299
|
+
next if href[0,1] =~ /[\#\{\[\<\%]/ # don't bother with anchors or special-looking links
|
265
300
|
|
266
301
|
begin
|
267
302
|
href = URI.parse(href)
|
@@ -294,9 +329,8 @@ public
|
|
294
329
|
|
295
330
|
# Check for an XHTML doctype
|
296
331
|
def is_xhtml?
|
297
|
-
intro = @doc.
|
298
|
-
is_xhtml = (intro =~ /w3c\/\/[\s]*dtd[\s]+xhtml/i)
|
299
|
-
is_xhtml = is_xhtml ? true : false
|
332
|
+
intro = @doc.to_html.strip.split("\n")[0..2].join(' ')
|
333
|
+
is_xhtml = !!(intro =~ /w3c\/\/[\s]*dtd[\s]+xhtml/i)
|
300
334
|
$stderr.puts "Is XHTML? #{is_xhtml.inspect}\nChecked:\n#{intro}" if @options[:debug]
|
301
335
|
is_xhtml
|
302
336
|
end
|
@@ -322,7 +356,7 @@ public
|
|
322
356
|
tags.each do |tag|
|
323
357
|
# skip links that look like they have merge tags
|
324
358
|
# and mailto, ftp, etc...
|
325
|
-
if tag.attributes[attribute].to_s =~ /^(
|
359
|
+
if tag.attributes[attribute].to_s =~ /^([\%\<\{\#\[]|data:|tel:|file:|sms:|callto:|facetime:|mailto:|ftp:|gopher:)/i
|
326
360
|
next
|
327
361
|
end
|
328
362
|
|
@@ -362,17 +396,16 @@ public
|
|
362
396
|
resolved = nil
|
363
397
|
if path =~ /(http[s]?|ftp):\/\//i
|
364
398
|
resolved = path
|
365
|
-
|
399
|
+
Premailer.canonicalize(resolved)
|
366
400
|
elsif base_path.kind_of?(URI)
|
367
401
|
resolved = base_path.merge(path)
|
368
|
-
|
402
|
+
Premailer.canonicalize(resolved)
|
369
403
|
elsif base_path.kind_of?(String) and base_path =~ /^(http[s]?|ftp):\/\//i
|
370
404
|
resolved = URI.parse(base_path)
|
371
405
|
resolved = resolved.merge(path)
|
372
|
-
|
406
|
+
Premailer.canonicalize(resolved)
|
373
407
|
else
|
374
|
-
|
375
|
-
return File.expand_path(path, File.dirname(base_path))
|
408
|
+
File.expand_path(path, File.dirname(base_path))
|
376
409
|
end
|
377
410
|
end
|
378
411
|
|
@@ -380,13 +413,9 @@ public
|
|
380
413
|
#
|
381
414
|
# IO objects return true, as do strings that look like URLs.
|
382
415
|
def self.local_data?(data)
|
383
|
-
if data.is_a?(IO) || data.is_a?(StringIO)
|
384
|
-
|
385
|
-
|
386
|
-
return false
|
387
|
-
else
|
388
|
-
return true
|
389
|
-
end
|
416
|
+
return true if data.is_a?(IO) || data.is_a?(StringIO)
|
417
|
+
return false if data =~ /^(http|https|ftp)\:\/\//i
|
418
|
+
true
|
390
419
|
end
|
391
420
|
|
392
421
|
# from http://www.ruby-forum.com/topic/140101
|
@@ -404,7 +433,7 @@ public
|
|
404
433
|
|
405
434
|
# Check <tt>CLIENT_SUPPORT_FILE</tt> for any CSS warnings
|
406
435
|
def check_client_support # :nodoc:
|
407
|
-
@client_support
|
436
|
+
@client_support ||= YAML::load(File.open(CLIENT_SUPPORT_FILE))
|
408
437
|
|
409
438
|
warnings = []
|
410
439
|
properties = []
|
@@ -450,3 +479,4 @@ public
|
|
450
479
|
return warnings
|
451
480
|
end
|
452
481
|
end
|
482
|
+
|
data/local-premailer
ADDED
data/premailer.gemspec
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = "premailer"
|
3
|
+
s.version = "1.7.3"
|
4
|
+
s.date = Time.now.strftime('%Y-%m-%d')
|
5
|
+
s.summary = "Preflight for HTML e-mail."
|
6
|
+
s.email = "code@dunae.ca"
|
7
|
+
s.homepage = "http://premailer.dialect.ca/"
|
8
|
+
s.description = "Improve the rendering of HTML emails by making CSS inline, converting links and warning about unsupported code."
|
9
|
+
s.has_rdoc = true
|
10
|
+
s.author = "Alex Dunae"
|
11
|
+
s.rdoc_options << '--all' << '--inline-source' << '--line-numbers' << '--charset' << 'utf-8'
|
12
|
+
s.extra_rdoc_files = ["README.rdoc"]
|
13
|
+
s.files = `git ls-files`.split("\n")
|
14
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
15
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
|
16
|
+
s.add_dependency('css_parser', '>= 1.1.9')
|
17
|
+
s.add_dependency('htmlentities', '>= 4.0.0')
|
18
|
+
s.add_development_dependency('hpricot', '>= 0.8.3')
|
19
|
+
s.add_development_dependency('nokogiri', '>= 1.4.4')
|
20
|
+
s.add_development_dependency('rake', ['~> 0.8', '!= 0.9.0'])
|
21
|
+
s.add_development_dependency('rdoc', '>= 2.4.2')
|
22
|
+
end
|
data/rakefile.rb
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
$:.unshift File.expand_path('../lib', __FILE__)
|
2
|
+
|
3
|
+
require 'rake'
|
4
|
+
require 'rake/testtask'
|
5
|
+
require 'rdoc/task'
|
6
|
+
require 'rubygems/package_task'
|
7
|
+
require 'fileutils'
|
8
|
+
require 'premailer'
|
9
|
+
|
10
|
+
def gemspec
|
11
|
+
@gemspec ||= begin
|
12
|
+
file = File.expand_path('../premailer.gemspec', __FILE__)
|
13
|
+
eval(File.read(file), binding, file)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
Gem::PackageTask.new(gemspec) do |pkg|
|
18
|
+
pkg.need_tar = true
|
19
|
+
end
|
20
|
+
|
21
|
+
desc 'Default: parse a URL.'
|
22
|
+
task :default => [:inline]
|
23
|
+
|
24
|
+
desc 'Parse a URL and write out the output.'
|
25
|
+
task :inline do
|
26
|
+
url = ENV['url']
|
27
|
+
output = ENV['output']
|
28
|
+
|
29
|
+
if !url or url.empty? or !output or output.empty?
|
30
|
+
puts 'Usage: rake inline url=http://example.com/ output=output.html'
|
31
|
+
exit
|
32
|
+
end
|
33
|
+
|
34
|
+
premailer = Premailer.new(url, :warn_level => Premailer::Warnings::SAFE, :verbose => true, :adapter => :nokogiri)
|
35
|
+
fout = File.open(output, "w")
|
36
|
+
fout.puts premailer.to_inline_css
|
37
|
+
fout.close
|
38
|
+
|
39
|
+
puts "Succesfully parsed '#{url}' into '#{output}'"
|
40
|
+
puts premailer.warnings.length.to_s + ' CSS warnings were found'
|
41
|
+
end
|
42
|
+
|
43
|
+
task :text do
|
44
|
+
url = ENV['url']
|
45
|
+
output = ENV['output']
|
46
|
+
|
47
|
+
if !url or url.empty? or !output or output.empty?
|
48
|
+
puts 'Usage: rake text url=http://example.com/ output=output.txt'
|
49
|
+
exit
|
50
|
+
end
|
51
|
+
|
52
|
+
premailer = Premailer.new(url, :warn_level => Premailer::Warnings::SAFE)
|
53
|
+
fout = File.open(output, "w")
|
54
|
+
fout.puts premailer.to_plain_text
|
55
|
+
fout.close
|
56
|
+
|
57
|
+
puts "Succesfully parsed '#{url}' into '#{output}'"
|
58
|
+
end
|
59
|
+
|
60
|
+
Rake::TestTask.new do |t|
|
61
|
+
t.test_files = FileList['test/test_*.rb']
|
62
|
+
t.verbose = false
|
63
|
+
end
|
64
|
+
|
65
|
+
RDoc::Task.new do |rd|
|
66
|
+
rd.main = "README.rdoc"
|
67
|
+
rd.rdoc_files.include("README.rdoc", "LICENSE.rdoc", "lib/**/*.rb")
|
68
|
+
rd.title = 'Premailer Documentation'
|
69
|
+
end
|
@@ -0,0 +1,142 @@
|
|
1
|
+
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
|
2
|
+
<!--
|
3
|
+
|
4
|
+
You can read this newsletter online at
|
5
|
+
[webversion]
|
6
|
+
|
7
|
+
-->
|
8
|
+
<html>
|
9
|
+
<head>
|
10
|
+
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
|
11
|
+
<title>Premailer Test</title>
|
12
|
+
<link rel="stylesheet" type="text/css" href="styles.css">
|
13
|
+
<style type="text/css">
|
14
|
+
@import "import.css" screen, handheld;
|
15
|
+
</style>
|
16
|
+
<style type="text/css">
|
17
|
+
@import "noimport.css" print;
|
18
|
+
</style>
|
19
|
+
<style type="text/css">
|
20
|
+
@media only screen and (max-device-width: 480px) {
|
21
|
+
#iphone { display: block; }
|
22
|
+
}
|
23
|
+
</style>
|
24
|
+
</head>
|
25
|
+
<body>
|
26
|
+
<div id="wrapper">
|
27
|
+
<p class="hide" id="hide01">This line should be hidden.</p>
|
28
|
+
<p class="hide" id="iphone">This is an iPhone style.</p>
|
29
|
+
<table width="646" class="container" cellspacing="0" cellpadding="0">
|
30
|
+
<tr><td id="webversion" colspan="6">Having trouble reading this newsletter? <webversion>Click here to see it in your browser</webversion></td></tr>
|
31
|
+
|
32
|
+
<tr><td height="13" colspan="6" class="frame"> </td></tr>
|
33
|
+
|
34
|
+
|
35
|
+
|
36
|
+
<table width="646" class="container" cellspacing="0" cellpadding="0">
|
37
|
+
|
38
|
+
<tr>
|
39
|
+
<td class="frame" width="13"> </td>
|
40
|
+
<td class="gutter" width="60"> </td>
|
41
|
+
|
42
|
+
<td class="content" colspan="2" width="500">
|
43
|
+
|
44
|
+
<h1><span>Premailer Test</span></h1>
|
45
|
+
|
46
|
+
<table width="500" cellpadding="0" cellspacing="0">
|
47
|
+
<tr>
|
48
|
+
<td width="20"> </td>
|
49
|
+
<td colspan="2" width="460">
|
50
|
+
<h2>Lorem ipsum dolor</h2>
|
51
|
+
<h3>Suspendisse id velit vitae ligula volutpat condimentum</h3>
|
52
|
+
<p class="dt">Morbi commodo, ipsum sed</p>
|
53
|
+
|
54
|
+
|
55
|
+
<p class="unaligned"><img src="2009-placeholder.png" alt="Image" align="right" class="right">Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Morbi commodo, ipsum sed pharetra gravida, orci magna rhoncus neque, id pulvinar odio lorem non turpis. Nullam sit amet enim. Suspendisse id velit vitae ligula volutpat condimentum. Aliquam erat volutpat. Sed quis velit. <a href="http://premailer.dialect.ca/">Nulla facilisi</a>. Nulla libero.</p>
|
56
|
+
|
57
|
+
<p attr="another quote">Here’s a quote. Here’s a quote. “Here’s a quote in quotes”.</p>
|
58
|
+
|
59
|
+
<p>Nullam sit amet enim. Suspendisse id velit vitae ligula volutpat condimentum. Aliquam erat volutpat. Sed quis velit. Nulla facilisi.</p>
|
60
|
+
<p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Morbi commodo, ipsum sed pharetra gravida, orci magna rhoncus neque, id pulvinar odio lorem non turpis. Nullam sit amet enim. Suspendisse id velit vitae ligula volutpat condimentum. Aliquam erat volutpat. Sed quis velit. Nulla facilisi. Nulla libero.</p>
|
61
|
+
<blockquote><p>“Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Morbi commodo, ipsum sed pharetra gravida, orci magna rhoncus neque, id pulvinar odio lorem non turpis.”</p></blockquote>
|
62
|
+
<p>Aliquam erat volutpat. Sed quis velit. Nulla facilisi. Nulla libero.</p>
|
63
|
+
|
64
|
+
<h3>Link tests</h3>
|
65
|
+
<ul>
|
66
|
+
<li><a id="l01" href="/">Relative path to root</a></li>
|
67
|
+
<li><a id="l02" href="http://premailer.dialect.ca/">Absolute path to root</a></li>
|
68
|
+
<li><a id="l03" href="http://example.com/">Different domain</a></li>
|
69
|
+
<li><a id="l04" href="images/">Relative path to sub-directory</a></li>
|
70
|
+
<li><a id="l05" href="#relative">Link is not converted</a></li>
|
71
|
+
<li><a id="l06" href="http://example.com/test.html?cn=tf&c=20&ord=%%RANDOM%%">Funky ASP URL</a></li>
|
72
|
+
<li><a id="l07" href="?query=string">Appends tracking query string</a></li>
|
73
|
+
<li><a id="l08" href="{DONOTCONVERT}">Link is not converted</a></li>
|
74
|
+
<li><a id="l09" href="[DONOTCONVERT]">Link is not converted</a></li>
|
75
|
+
<li><a id="l10" href="<DONOTCONVERT>">Link is not converted</a></li>
|
76
|
+
<li><a id="l11" href="mailto:premailer@example.com">mailto link</a></li>
|
77
|
+
<li><a id="l12" href="ftp://example.com">FTP link</a></li>
|
78
|
+
<li><a id="l13" href="gopher://gopher.floodgap.com/1/fun/twitpher">Gopher link</a></li>
|
79
|
+
</ul>
|
80
|
+
|
81
|
+
|
82
|
+
|
83
|
+
<p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Morbi commodo, ipsum sed pharetra gravida, orci magna rhoncus neque, id pulvinar odio lorem non turpis.</p>
|
84
|
+
|
85
|
+
<p> </p>
|
86
|
+
|
87
|
+
|
88
|
+
</td>
|
89
|
+
<td width="20"> </td>
|
90
|
+
</tr>
|
91
|
+
</table>
|
92
|
+
|
93
|
+
<p class="section"><img src="dots_end.png" alt="---" width="499" height="75"></p>
|
94
|
+
</td><!-- /#content -->
|
95
|
+
<td class="gutter" width="60"> </td>
|
96
|
+
|
97
|
+
<td class="frame" width="13"> </td>
|
98
|
+
</tr>
|
99
|
+
|
100
|
+
<tr>
|
101
|
+
<td class="frame" width="13"> </td>
|
102
|
+
<td colspan="4" width="620">
|
103
|
+
<table summary="Contact information" cellspacing="0" cellpadding="0" width="620">
|
104
|
+
<tr><td height="4" class="hairline"> </td></tr>
|
105
|
+
<tr><td height="34"class="contact"> </td></tr>
|
106
|
+
<tr>
|
107
|
+
|
108
|
+
<td align="center" class="contact" id="contact_info">
|
109
|
+
<p id="address">Premailer Test<br>
|
110
|
+
<a href="http://dialect.ca/?utm_source=Premailer&utm_medium=Test+Suite&utm_campaign=Premailer">by Dialect</a><br>
|
111
|
+
Vancouver Island, British Columbia<br>
|
112
|
+
250 555.2222</p>
|
113
|
+
</td>
|
114
|
+
|
115
|
+
</tr>
|
116
|
+
<tr><td height="34"class="contact"> </td></tr>
|
117
|
+
<tr><td height="4" class="hairline"> </td></tr>
|
118
|
+
</table>
|
119
|
+
</td>
|
120
|
+
<td class="frame" width="13"> </td>
|
121
|
+
</tr>
|
122
|
+
|
123
|
+
<tr>
|
124
|
+
<td class="frame" width="13"> </td>
|
125
|
+
|
126
|
+
<td colspan="4" class="content" height="60"> </td>
|
127
|
+
<td class="frame" width="13"> </td>
|
128
|
+
</tr>
|
129
|
+
|
130
|
+
|
131
|
+
|
132
|
+
|
133
|
+
|
134
|
+
<tr><td height="13" colspan="6" class="frame"> </td></tr>
|
135
|
+
<tr><td height="22" colspan="6" > </td></tr>
|
136
|
+
<tr><td id="credit" colspan="6">Newsletter communications by<br><a href="http://dialect.ca/dialogue/?utm_source=Dialogue&utm_medium=Credit&utm_campaign=South+Hollow"><img src="inc/dialect.png" alt="Dialect" width="60" height="35" border="0"></a><br><unsubscribe>Click here to unsubscribe</unsubscribe></td></tr>
|
137
|
+
|
138
|
+
</table>
|
139
|
+
|
140
|
+
</div>
|
141
|
+
</body>
|
142
|
+
</html>
|