premailer 1.5.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,49 @@
1
+ = Premailer CHANGELOG
2
+
3
+ == Version 1.5.2
4
+ * released to GitHub
5
+ * fixed handling of mailto links
6
+ * various minor updates
7
+
8
+ == Version 1.5.1
9
+ * bugfix (http://code.google.com/p/premailer/issues/detail?id=1 and http://code.google.com/p/premailer/issues/detail?id=2) thanks to Russell Norris
10
+ * bugfix (http://code.google.com/p/premailer/issues/detail?id=4) thanks to Dave Holmes
11
+
12
+ == Version 1.5.0
13
+ * preview release of Ruby gem
14
+
15
+ == Version 1.4
16
+ * incremental parsing improvements
17
+ * respect <tt>@media</tt> rule (http://www.w3.org/TR/CSS21/media.html#at-media-rule)
18
+ * better quote escaping
19
+
20
+ == Version 1.3
21
+ * separate CSS parser into its own library
22
+ * handle <tt>background: red url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAADElEQVR42mP4%2F58BAAT%2FAf9jgNErAAAAAElFTkSuQmCC);</tt>
23
+ * preserve <tt>:hover</tt> etc... in head styles
24
+
25
+ == Version 1.2
26
+ * respect <tt>LINK</tt> media types
27
+ * better style folding
28
+ * incremental parsing improvements
29
+
30
+ == Version 1.1
31
+ * proper calculation of selector specificity per CSS 2.1 spec
32
+ * support for <tt>@import</tt>
33
+ * preliminary support for shorthand CSS properties (<tt>margin</tt>, <tt>padding</tt>)
34
+ * preliminary separation of CSS parser
35
+
36
+ == Version 1.0
37
+ * ported web interface to eRuby
38
+ * incremental parsing improvements
39
+
40
+ == Version 0.9
41
+ * initial proof-of-concept
42
+ * PHP web version
43
+
44
+ == TODO: Future
45
+ * complete shorthand properties support (<tt>border-width</tt>, <tt>font</tt>, <tt>background</tt>)
46
+ * UTF-8 and other charsets (test page: http://kianga.kcore.de/2004/09/21/utf8_test)
47
+ * make warnings for <tt>border</tt> match <tt>border-left</tt>, etc...
48
+ * Integrate CSS validator
49
+ * Remove unused classes and IDs
@@ -0,0 +1,42 @@
1
+ = Premailer License
2
+
3
+ Copyright (c) 2007-09 Alex Dunae
4
+
5
+ Premailer is copyrighted free software by Alex Dunae (http://dunae.ca/).
6
+ You can redistribute it and/or modify it under the conditions below:
7
+
8
+ 1. You may make and give away verbatim copies of the source form of the
9
+ software without restriction, provided that you duplicate all of the
10
+ original copyright notices and associated disclaimers.
11
+
12
+ 2. You may modify your copy of the software in any way, provided that
13
+ you do at least ONE of the following:
14
+
15
+ a) place your modifications in the Public Domain or otherwise
16
+ make them Freely Available, such as by posting said
17
+ modifications to the internet or an equivalent medium, or by
18
+ allowing the author to include your modifications in the software.
19
+
20
+ b) use the modified software only within your corporation or
21
+ organization.
22
+
23
+ c) rename any non-standard executables so the names do not conflict
24
+ with standard executables, which must also be provided.
25
+
26
+ d) make other distribution arrangements with the author.
27
+
28
+ 3. You may modify and include the part of the software into any other
29
+ software (possibly commercial) as long as clear acknowledgement and
30
+ a link back to the original software (http://code.dunae.ca/premailer.web/)
31
+ is provided.
32
+
33
+ 5. The scripts and library files supplied as input to or produced as
34
+ output from the software do not automatically fall under the
35
+ copyright of the software, but belong to whomever generated them,
36
+ and may be sold commercially, and may be aggregated with this
37
+ software.
38
+
39
+ 6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
40
+ IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
41
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
42
+ PURPOSE.
@@ -0,0 +1,66 @@
1
+ = Premailer README
2
+
3
+ === What is this?
4
+
5
+ For the best HTML e-mail delivery results, CSS should be inline. This is a
6
+ huge pain and a simple newsletter becomes un-managable very quickly. This
7
+ script is my solution.
8
+
9
+ * CSS styles are converted to inline style attributes
10
+ Checks style and link[rel=stylesheet] tags and preserves existing inline attributes
11
+ * Relative paths are converted to absolute paths
12
+ Checks links in href, src and CSS url('')
13
+ * CSS properties are checked against e-mail client capabilities
14
+ Based on the Email Standards Project's guides
15
+ * A plain text version is created
16
+ Optional
17
+
18
+
19
+ === Installation
20
+
21
+ Download the Premailer gem from GemCutter.
22
+
23
+ gem sources -a http://gemcutter.org
24
+ sudo gem install premailer
25
+
26
+ === Example
27
+ premailer = Premailer.new('http://example.com/myfile.html', :warn_level => Premailer::Warnings::SAFE)
28
+
29
+ # Write the HTML output
30
+ fout = File.open("output.html", "w")
31
+ fout.puts premailer.to_inline_css
32
+ fout.close
33
+
34
+ # Write the plain-text output
35
+ fout = File.open("ouput.txt", "w")
36
+ fout.puts premailer.to_plain_text
37
+ fout.close
38
+
39
+ # Output any CSS warnings
40
+ premailer.warnings.each do |w|
41
+ puts "#{w[:message]} (#{w[:level]}) may not render properly in #{w[:clients]}"
42
+ end
43
+
44
+ === Contributions
45
+
46
+ Contributions are most welcome. Premailer was rotting away in a private SVN repository for too long and could use some TLC. Pull and patch to your heart's content.
47
+
48
+ A few areas that are particularly in need of love:
49
+ * Testing suite
50
+ There were unit tests but they were so funky that it was better to just strip them out.
51
+ * Test running Premailer on local files
52
+ * Create a binary file for easing command line use, allowing the output to be piped in *nix systems
53
+ * Ruby 1.9 testing
54
+ * Test with Rails
55
+ * Move un-repeated background images defined in CSS to <tt><td background=""></tt> for Outlook
56
+ * Correctly parse http://www.webstandards.org/files/acid2/test.html
57
+
58
+ === Credits and code
59
+
60
+ Premailer is written in Ruby.
61
+
62
+ The web interface can be found at http://premailer.dialect.ca/ .
63
+
64
+ The source code can be found at http://github.com/alexdunae/premailer .
65
+
66
+ Written by Alex Dunae (dunae.ca, e-mail 'code' at the same domain), 2008-2009.
@@ -0,0 +1,9 @@
1
+ # Premailer by Alex Dunae (dunae.ca, e-mail 'code' at the same domain), 2008-09
2
+
3
+ require 'yaml'
4
+ require 'open-uri'
5
+ require 'hpricot'
6
+ require 'css_parser'
7
+
8
+ require File.dirname(__FILE__) + "/premailer/html_to_plain_text"
9
+ require File.dirname(__FILE__) + "/premailer/premailer"
@@ -0,0 +1,58 @@
1
+ require 'text/reform'
2
+ require 'htmlentities'
3
+
4
+ # Support functions for Premailer
5
+ module HtmlToPlainText
6
+
7
+ # Returns the text in UTF-8 format with all HTML tags removed
8
+ #
9
+ # TODO:
10
+ # - add support for DL, OL
11
+ def convert_to_text(html, line_length, from_charset = 'UTF-8')
12
+ r = Text::Reform.new(:trim => true,
13
+ :squeeze => false,
14
+ :break => Text::Reform.break_wrap)
15
+
16
+ txt = html
17
+
18
+ he = HTMLEntities.new # decode HTML entities
19
+
20
+ txt = he.decode(txt)
21
+
22
+ txt.gsub!(/<h([0-9]+)[^>]*>(.*)<\/h[0-9]+>/i) do |s| # handle headings
23
+ hlevel = $1.to_i
24
+ htext = $2.gsub(/<\/?[^>]*>/i, '') # remove tags inside headings
25
+ hlength = (htext.length > line_length ?
26
+ line_length :
27
+ htext.length)
28
+
29
+ case hlevel
30
+ when 1 # H1
31
+ ('*' * hlength) + "\n" + htext + "\n" + ('*' * hlength) + "\n"
32
+ when 2 # H2
33
+ ('-' * hlength) + "\n" + htext + "\n" + ('-' * hlength) + "\n"
34
+ else # H3-H6 are styled the same
35
+ htext + "\n" + ('-' * htext.length) + "\n"
36
+ end
37
+ end
38
+
39
+ txt.gsub!(/<a.*href=\"([^\"]*)\"[^>]*>(.*)<\/a>/i) do |s| # links
40
+ $2 + ' [' + $1 + ']'
41
+ end
42
+
43
+ txt.gsub!(/(<li[\s]+[^>]*>|<li>)/i, ' * ') # unordered LIsts
44
+ txt.gsub!(/<\/p>/i, "\n\n") # paragraphs
45
+
46
+ txt.gsub!(/<\/?[^>]*>/, '') # strip remaining tags
47
+ txt.gsub!(/\A[\s]+|[\s]+\Z|^[ \t]+/m, '') # strip extra spaces
48
+ txt.gsub!(/[\n]{3,}/m, "\n\n") # tighten line breaks
49
+
50
+ txt = r.format(('[' * line_length), txt) # wrap text
51
+ txt.gsub!(/^[\*][\s]/m, ' * ') # add spaces back to lists
52
+
53
+ txt.gsub!(/^\s+$/, "\n") # \r\n and \r -> \n
54
+ txt.gsub!(/\r\n?/, "\n") # \r\n and \r -> \n
55
+ txt.gsub!(/[\n]{3,}/, "\n")
56
+ txt
57
+ end
58
+ end
@@ -0,0 +1,393 @@
1
+ #!/usr/bin/ruby
2
+ #
3
+ # Premailer by Alex Dunae (dunae.ca, e-mail 'code' at the same domain), 2008-09
4
+ #
5
+ # Premailer processes HTML and CSS to improve e-mail deliverability.
6
+ #
7
+ # Premailer's main function is to render all CSS as inline <tt>style</tt>
8
+ # attributes. It also converts relative links to absolute links and checks
9
+ # the 'safety' of CSS properties against a CSS support chart.
10
+ #
11
+ # = Example
12
+ # premailer = Premailer.new('http://example.com/myfile.html', :warn_level => Premailer::Warnings::SAFE)
13
+ #
14
+ # # Write the HTML output
15
+ # fout = File.open("output.html", "w")
16
+ # fout.puts premailer.to_inline_css
17
+ # fout.close
18
+ #
19
+ # # Write the plain-text output
20
+ # fout = File.open("ouput.txt", "w")
21
+ # fout.puts premailer.to_plain_text
22
+ # fout.close
23
+ #
24
+ # # List any CSS warnings
25
+ # puts premailer.warnings.length.to_s + ' warnings found'
26
+ # premailer.warnings.each do |w|
27
+ # puts "#{w[:message]} (#{w[:level]}) may not render properly in #{w[:clients]}"
28
+ # end
29
+ #
30
+ # premailer = Premailer.new(html_file, :warn_level => Premailer::Warnings::SAFE)
31
+ # puts premailer.to_inline_css
32
+ class Premailer
33
+ include HtmlToPlainText
34
+ include CssParser
35
+
36
+ VERSION = '1.5.2'
37
+
38
+ CLIENT_SUPPORT_FILE = File.dirname(__FILE__) + '/../../misc/client_support.yaml'
39
+
40
+ RE_UNMERGABLE_SELECTORS = /(\:(visited|active|hover|focus|after|before|selection|target|first\-(line|letter))|^\@)/i
41
+
42
+ # should also exclude :first-letter, etc...
43
+
44
+ # URI of the HTML file used
45
+ attr_reader :html_file
46
+
47
+ module Warnings
48
+ NONE = 0
49
+ SAFE = 1
50
+ POOR = 2
51
+ RISKY = 3
52
+ end
53
+ include Warnings
54
+
55
+ WARN_LABEL = %w(NONE SAFE POOR RISKY)
56
+
57
+ # Create a new Premailer object.
58
+ #
59
+ # +path+ is the path to the HTML file to process. Can be either the URL of a
60
+ # remote file or a local path.
61
+ #
62
+ # ==== Options
63
+ # [+line_length+] Line length used by to_plain_text. Boolean, default is 65.
64
+ # [+warn_level+] What level of CSS compatibility warnings to show (see Warnings).
65
+ # [+link_query_string+] A string to append to every <a href=""> link. Do not include the initial +?+.
66
+ # [+base_url+] Used to calculate absolute URLs for local files.
67
+ def initialize(path, options = {})
68
+ @options = {:warn_level => Warnings::SAFE,
69
+ :line_length => 65,
70
+ :link_query_string => nil,
71
+ :base_url => nil,
72
+ :remove_classes => false}.merge(options)
73
+ @html_file = path
74
+
75
+ @is_local_file = true
76
+ if path =~ /^(http|https|ftp)\:\/\//i
77
+ @is_local_file = false
78
+ end
79
+
80
+ @css_warnings = []
81
+
82
+ @css_parser = CssParser::Parser.new({:absolute_paths => true,
83
+ :import => true,
84
+ :io_exceptions => false
85
+ })
86
+
87
+ @doc, @html_charset = load_html(@html_file)
88
+
89
+ if @is_local_file and @options[:base_url]
90
+ @doc = convert_inline_links(@doc, @options[:base_url])
91
+ elsif not @is_local_file
92
+ @doc = convert_inline_links(@doc, @html_file)
93
+ end
94
+ load_css_from_html!
95
+ end
96
+
97
+ # Array containing a hash of CSS warnings.
98
+ def warnings
99
+ return [] if @options[:warn_level] == Warnings::NONE
100
+ @css_warnings = check_client_support if @css_warnings.empty?
101
+ @css_warnings
102
+ end
103
+
104
+ # Returns the original HTML as a string.
105
+ def to_s
106
+ @doc.to_html
107
+ end
108
+
109
+ # Converts the HTML document to a format suitable for plain-text e-mail.
110
+ #
111
+ # Returns a string.
112
+ def to_plain_text
113
+ html_src = ''
114
+ begin
115
+ html_src = @doc.search("body").innerHTML
116
+ rescue
117
+ html_src = @doc.to_html
118
+ end
119
+ convert_to_text(html_src, @options[:line_length], @html_charset)
120
+ end
121
+
122
+ # Merge CSS into the HTML document.
123
+ #
124
+ # Returns a string.
125
+ def to_inline_css
126
+ doc = @doc
127
+ unmergable_rules = CssParser::Parser.new
128
+
129
+ # Give all styles already in style attributes a specificity of 1000
130
+ # per http://www.w3.org/TR/CSS21/cascade.html#specificity
131
+ doc.search("*[@style]").each do |el|
132
+ el['style'] = '[SPEC=1000[' + el.attributes['style'] + ']]'
133
+ end
134
+
135
+ # Iterate through the rules and merge them into the HTML
136
+ @css_parser.each_selector(:all) do |selector, declaration, specificity|
137
+ # Save un-mergable rules separately
138
+ selector.gsub!(/:link([\s]|$)+/i, '')
139
+
140
+ # Convert element names to lower case
141
+ selector.gsub!(/([\s]|^)([\w]+)/) {|m| $1.to_s + $2.to_s.downcase }
142
+
143
+ if selector =~ RE_UNMERGABLE_SELECTORS
144
+ unmergable_rules.add_rule_set!(RuleSet.new(selector, declaration))
145
+ else
146
+
147
+ doc.search(selector) do |el|
148
+ if el.elem?
149
+ # Add a style attribute or append to the existing one
150
+ block = "[SPEC=#{specificity}[#{declaration}]]"
151
+ el['style'] = (el.attributes['style'] ||= '') + ' ' + block
152
+ end
153
+ end
154
+ end
155
+ end
156
+
157
+ # Read <style> attributes and perform folding
158
+ doc.search("*[@style]").each do |el|
159
+ style = el.attributes['style'].to_s
160
+
161
+ declarations = []
162
+
163
+ style.scan(/\[SPEC\=([\d]+)\[(.[^\]\]]*)\]\]/).each do |declaration|
164
+ rs = RuleSet.new(nil, declaration[1].to_s, declaration[0].to_i)
165
+ declarations << rs
166
+ end
167
+
168
+ # Perform style folding and save
169
+ merged = CssParser.merge(declarations)
170
+
171
+ el['style'] = Premailer.escape_string(merged.declarations_to_s)
172
+ end
173
+
174
+ doc = write_unmergable_css_rules(doc, unmergable_rules)
175
+
176
+ doc.search('*').remove_class if @options[:remove_classes]
177
+
178
+ doc.to_html
179
+ end
180
+
181
+
182
+ protected
183
+ # Load the HTML file and convert it into an Hpricot document.
184
+ #
185
+ # Returns an Hpricot document and a string with the HTML file's character set.
186
+ def load_html(path) # :nodoc:
187
+ if @is_local_file
188
+ Hpricot(File.open(path, "r") {|f| f.read })
189
+ else
190
+ Hpricot(open(path))
191
+ end
192
+ end
193
+
194
+ # Load CSS included in <tt>style</tt> and <tt>link</tt> tags from an HTML document.
195
+ def load_css_from_html! # :nodoc:
196
+ if tags = @doc.search("link[@rel='stylesheet'], style")
197
+ tags.each do |tag|
198
+
199
+ if tag.to_s.strip =~ /^\<link/i and tag.attributes['href'] and media_type_ok?(tag.attributes['media'])
200
+
201
+ link_uri = Premailer.resolve_link(tag.attributes['href'].to_s, @html_file)
202
+ if @is_local_file
203
+ css_block = ''
204
+ begin
205
+ File.open(link_uri, "r") do |file|
206
+ while line = file.gets
207
+ css_block << line
208
+ end
209
+ end
210
+ @css_parser.add_block!(css_block, {:base_uri => @html_file})
211
+ rescue; end
212
+ else
213
+ @css_parser.load_uri!(link_uri)
214
+ end
215
+
216
+ elsif tag.to_s.strip =~ /^\<style/i
217
+ @css_parser.add_block!(tag.innerHTML, :base_uri => URI.parse(@html_file))
218
+ end
219
+ end
220
+ tags.remove
221
+ end
222
+ end
223
+
224
+ def media_type_ok?(media_types) # :nodoc:
225
+ return media_types.split(/[\s]+|,/).any? { |media_type| media_type.strip =~ /screen|handheld|all/i }
226
+ rescue
227
+ return true
228
+ end
229
+
230
+ # Create a <tt>style</tt> element with un-mergable rules (e.g. <tt>:hover</tt>)
231
+ # and write it into the <tt>body</tt>.
232
+ #
233
+ # <tt>doc</tt> is an Hpricot document and <tt>unmergable_css_rules</tt> is a Css::RuleSet.
234
+ #
235
+ # Returns an Hpricot document.
236
+ def write_unmergable_css_rules(doc, unmergable_rules) # :nodoc:
237
+ styles = ''
238
+ unmergable_rules.each_selector(:all, :force_important => true) do |selector, declarations, specificity|
239
+ styles += "#{selector} { #{declarations} }\n"
240
+ end
241
+
242
+ unless styles.empty?
243
+ style_tag = "\n<style type=\"text/css\">\n#{styles}</style>\n"
244
+ doc.search("head").append(style_tag)
245
+ end
246
+ doc
247
+ end
248
+
249
+ # Convert relative links to absolute links.
250
+ #
251
+ # Processes <tt>href</tt> <tt>src</tt> and <tt>background</tt> attributes
252
+ # as well as CSS <tt>url()</tt> declarations found in inline <tt>style</tt> attributes.
253
+ #
254
+ # <tt>doc</tt> is an Hpricot document and <tt>base_uri</tt> is either a string or a URI.
255
+ #
256
+ # Returns an Hpricot document.
257
+ def convert_inline_links(doc, base_uri) # :nodoc:
258
+ base_uri = URI.parse(base_uri) unless base_uri.kind_of?(URI)
259
+
260
+ append_qs = @options[:link_query_string] ||= ''
261
+
262
+ ['href', 'src', 'background'].each do |attribute|
263
+ tags = doc.search("*[@#{attribute}]")
264
+
265
+ next if tags.empty?
266
+
267
+ tags.each do |tag|
268
+
269
+ # skip links that look like they have merge tags
270
+ # and mailto, ftp, etc...
271
+ if tag.attributes[attribute] =~ /^(\{|\[|<|\#|mailto:|ftp:|gopher:)/i
272
+ next
273
+ end
274
+
275
+ if tag.attributes[attribute] =~ /^http/i
276
+ begin
277
+ merged = URI.parse(tag.attributes[attribute])
278
+ rescue; next; end
279
+ else
280
+ begin
281
+ merged = Premailer.resolve_link(tag.attributes[attribute].to_s, base_uri)
282
+ rescue
283
+ begin
284
+ merged = Premailer.resolve_link(URI.escape(tag.attributes[attribute].to_s), base_uri)
285
+ rescue; end
286
+ end
287
+ end
288
+
289
+ # make sure 'merged' is a URI
290
+ merged = URI.parse(merged.to_s) unless merged.kind_of?(URI)
291
+
292
+ # only append a querystring to <a> tags
293
+ if tag.name =~ /^a$/i and not append_qs.empty?
294
+ if merged.query
295
+ merged.query = merged.query + '&' + append_qs
296
+ else
297
+ merged.query = append_qs
298
+ end
299
+ end
300
+ tag[attribute] = merged.to_s
301
+
302
+ end # end of each tag
303
+ end # end of each attrs
304
+
305
+ doc.search("*[@style]").each do |el|
306
+ el['style'] = CssParser.convert_uris(el.attributes['style'].to_s, base_uri)
307
+ end
308
+ doc
309
+ end
310
+
311
+ def self.escape_string(str) # :nodoc:
312
+ str.gsub(/"/, "'")
313
+ end
314
+
315
+ def self.resolve_link(path, base_path) # :nodoc:
316
+ resolved = nil
317
+ if base_path.kind_of?(URI)
318
+ resolved = base_path.merge(path)
319
+ return Premailer.canonicalize(resolved)
320
+ elsif base_path.kind_of?(String) and base_path =~ /^(http[s]?|ftp):\/\//i
321
+ resolved = URI.parse(base_path)
322
+ resolved = resolved.merge(path)
323
+ return Premailer.canonicalize(resolved)
324
+ else
325
+
326
+ return File.expand_path(path, File.dirname(base_path))
327
+ end
328
+ end
329
+
330
+ # from http://www.ruby-forum.com/topic/140101
331
+ def self.canonicalize(uri) # :nodoc:
332
+ u = uri.kind_of?(URI) ? uri : URI.parse(uri.to_s)
333
+ u.normalize!
334
+ newpath = u.path
335
+ while newpath.gsub!(%r{([^/]+)/\.\./?}) { |match|
336
+ $1 == '..' ? match : ''
337
+ } do end
338
+ newpath = newpath.gsub(%r{/\./}, '/').sub(%r{/\.\z}, '/')
339
+ u.path = newpath
340
+ u.to_s
341
+ end
342
+
343
+ # Check <tt>CLIENT_SUPPORT_FILE</tt> for any CSS warnings
344
+ def check_client_support # :nodoc:
345
+ @client_support = @client_support ||= YAML::load(File.open(CLIENT_SUPPORT_FILE))
346
+
347
+ warnings = []
348
+ properties = []
349
+
350
+ # Get a list off CSS properties
351
+ @doc.search("*[@style]").each do |el|
352
+ style_url = el.attributes['style'].gsub(/([\w\-]+)[\s]*\:/i) do |s|
353
+ properties.push($1)
354
+ end
355
+ end
356
+
357
+ properties.uniq!
358
+
359
+ property_support = @client_support['css_properties']
360
+ properties.each do |prop|
361
+ if property_support.include?(prop) and
362
+ property_support[prop].include?('support') and
363
+ property_support[prop]['support'] >= @options[:warn_level]
364
+ warnings.push({:message => "#{prop} CSS property",
365
+ :level => WARN_LABEL[property_support[prop]['support']],
366
+ :clients => property_support[prop]['unsupported_in'].join(', ')})
367
+ end
368
+ end
369
+
370
+ @client_support['attributes'].each do |attribute, data|
371
+ next unless data['support'] >= @options[:warn_level]
372
+ if @doc.search("*[@#{attribute}]").length > 0
373
+ warnings.push({:message => "#{attribute} HTML attribute",
374
+ :level => WARN_LABEL[property_support[prop]['support']],
375
+ :clients => property_support[prop]['unsupported_in'].join(', ')})
376
+ end
377
+ end
378
+
379
+ @client_support['elements'].each do |element, data|
380
+ next unless data['support'] >= @options[:warn_level]
381
+ if @doc.search("element").length > 0
382
+ warnings.push({:message => "#{element} HTML element",
383
+ :level => WARN_LABEL[property_support[prop]['support']],
384
+ :clients => property_support[prop]['unsupported_in'].join(', ')})
385
+ end
386
+ end
387
+
388
+ return warnings
389
+ end
390
+ end
391
+
392
+
393
+
@@ -0,0 +1,230 @@
1
+ # Capabilities of e-mail clients
2
+ #
3
+ # Sources
4
+ # * http://campaignmonitor.com/css/
5
+ # * http://www.campaignmonitor.com/blog/archives/2007/04/a_guide_to_css_support_in_emai_2.html
6
+ # * http://www.campaignmonitor.com/blog/archives/2007/11/do_image_maps_work_in_html_ema.html
7
+ # * http://www.campaignmonitor.com/blog/archives/2007/11/how_forms_perform_in_html_emai.html
8
+ # * http://www.xavierfrenette.com/articles/css-support-in-webmail/
9
+ # * http://www.email-standards.org/
10
+ # Updated 2008-08-26
11
+ #
12
+ # Support: 1 = SAFE, 2 = POOR, 3 = RISKY
13
+ elements:
14
+ map:
15
+ support: 2
16
+ unsupported_in: [GMail]
17
+ area:
18
+ support: 2
19
+ unsupported_in: [GMail]
20
+ form:
21
+ support: 3
22
+ unsupported_in: [Mobile Me, Old Yahoo, AOL, Live Mail, Outlook 07, Outlook 03]
23
+ link:
24
+ support: 2
25
+ unsupported_in: [GMail, Hotmail, Old Yahoo]
26
+ attributes:
27
+ ismap:
28
+ support: 2
29
+ unsupported_in: [GMail]
30
+ css_properties:
31
+ color:
32
+ unsupported_in: [Eudora]
33
+ support_level: 92%
34
+ support: 1
35
+ font-size:
36
+ unsupported_in: [Eudora]
37
+ support_level: 92%
38
+ support: 1
39
+ font-style:
40
+ unsupported_in: [Eudora]
41
+ support_level: 92%
42
+ support: 1
43
+ font-weight:
44
+ unsupported_in: [Eudora]
45
+ support_level: 92%
46
+ support: 1
47
+ text-align:
48
+ unsupported_in: [Eudora]
49
+ support_level: 92%
50
+ support: 1
51
+ text-decoration:
52
+ unsupported_in: [Eudora]
53
+ support_level: 92%
54
+ support: 1
55
+ background-color:
56
+ unsupported_in: [Notes 6, Eudora]
57
+ support_level: 85%
58
+ support: 2
59
+ border: &border_shorthand
60
+ unsupported_in: [Notes 6, Eudora]
61
+ support_level: 85%
62
+ support: 2
63
+ border-bottom: *border_shorthand
64
+ border-left: *border_shorthand
65
+ border-right: *border_shorthand
66
+ border-top: *border_shorthand
67
+ display:
68
+ unsupported_in: [Outlook 07, Eudora]
69
+ support_level: 85%
70
+ support: 2
71
+ font-family:
72
+ unsupported_in: [Eudora, Old GMail, New GMail]
73
+ support_level: 92%
74
+ support: 2
75
+ font-variant:
76
+ unsupported_in: [Notes 6, Eudora]
77
+ support_level: 85%
78
+ support: 2
79
+ letter-spacing:
80
+ unsupported_in: [Notes 6, Eudora]
81
+ support_level: 85%
82
+ support: 2
83
+ line-height:
84
+ unsupported_in: [Notes 6, Eudora]
85
+ support_level: 85%
86
+ support: 2
87
+ padding: &padding_shorthand
88
+ unsupported_in: [Notes 6, Eudora]
89
+ support_level: 85%
90
+ support: 2
91
+ padding-bottom: *padding_shorthand
92
+ padding-left: *padding_shorthand
93
+ padding-right: *padding_shorthand
94
+ padding-top: *padding_shorthand
95
+ table-layout:
96
+ unsupported_in: [Notes 6, Eudora]
97
+ support_level: 85%
98
+ support: 2
99
+ text-indent:
100
+ unsupported_in: [Notes 6, Eudora]
101
+ support_level: 85%
102
+ support: 2
103
+ text-transform:
104
+ unsupported_in: [Notes 6, Eudora]
105
+ support_level: 85%
106
+ support: 2
107
+ border-collapse:
108
+ unsupported_in: [Entourage 2004, Notes 6, Eudora]
109
+ support_level: 77%
110
+ support: 3
111
+ clear:
112
+ unsupported_in: [Outlook 07, Notes 6, Eudora]
113
+ support_level: 77%
114
+ support: 3
115
+ direction:
116
+ unsupported_in: [Outlook 07, Entourage 2004, Eudora, New GMail]
117
+ support_level: 77%
118
+ support: 3
119
+ float:
120
+ unsupported_in: [Outlook 07, Eudora, Old GMail]
121
+ support_level: 85%
122
+ support: 3
123
+ vertical-align:
124
+ unsupported_in: [Outlook 07, Notes 6, Eudora]
125
+ support_level: 77%
126
+ support: 3
127
+ width:
128
+ unsupported_in: [Outlook 07, Notes 6, Eudora]
129
+ support_level: 77%
130
+ support: 3
131
+ word-spacing:
132
+ unsupported_in: [Outlook 07, Notes 6, Eudora]
133
+ support_level: 77%
134
+ support: 3
135
+ height:
136
+ unsupported_in: [Outlook 07, Notes 6, Eudora, Old GMail]
137
+ support_level: 77%
138
+ support: 3
139
+ list-style-type:
140
+ unsupported_in: [Outlook 07, Eudora, Hotmail]
141
+ support_level: 85%
142
+ support: 3
143
+ overflow:
144
+ unsupported_in: [Outlook 07, Entourage 2004, Notes 6, Eudora]
145
+ support_level: 69%
146
+ support: 3
147
+ visibility:
148
+ unsupported_in: [Outlook 07, Notes 6, Eudora, Old GMail, New GMail, aolWeb]
149
+ support_level: 77%
150
+ support: 3
151
+ white-space:
152
+ unsupported_in: [Outlook 03, Windows Mail, AOL 9, AOL 10, Notes 6, Eudora, Mobile Me]
153
+ support_level: 54%
154
+ support: 3
155
+ background-image:
156
+ unsupported_in: [Outlook 07, Notes 6, Eudora, Old GMail, New GMail, Live Mail]
157
+ support_level: 77%
158
+ support: 3
159
+ background-repeat:
160
+ unsupported_in: [Outlook 07, Notes 6, Eudora, Old GMail, New GMail, Live Mail]
161
+ support_level: 77%
162
+ support: 3
163
+ clip:
164
+ unsupported_in: [Outlook 07, Notes 6, Eudora, New Yahoo, New GMail, Live Mail, Mobile Me]
165
+ support_level: 77%
166
+ support: 3
167
+ cursor:
168
+ unsupported_in: [Outlook 07, Entourage 2004, Notes 6, Eudora, Old GMail, New GMail]
169
+ support_level: 69%
170
+ support: 3
171
+ list-style-image:
172
+ unsupported_in: [Outlook 07, Notes 6, Eudora, Old GMail, New GMail, Live Mail]
173
+ support_level: 77%
174
+ support: 3
175
+ list-style-position:
176
+ unsupported_in: [Outlook 07, Notes 6, Eudora, Old Yahoo, Hotmail]
177
+ support_level: 77%
178
+ support: 3
179
+ margin: &margin_shorthand
180
+ unsupported_in: [AOL 9, Notes 6, Eudora, Live Mail, Hotmail]
181
+ support_level: 77%
182
+ support: 3
183
+ margin-bottom: *margin_shorthand
184
+ margin-left: *margin_shorthand
185
+ margin-right: *margin_shorthand
186
+ margin-top: *margin_shorthand
187
+ z-index:
188
+ unsupported_in: [Notes 6, Eudora, New Yahoo, Old GMail, New GMail, Live Mail]
189
+ support_level: 85%
190
+ support: 3
191
+ left:
192
+ unsupported_in: [Outlook 07, Notes 6, Eudora, New Yahoo, Old GMail, New GMail, Live Mail]
193
+ support_level: 77%
194
+ support: 3
195
+ right:
196
+ unsupported_in: [Outlook 07, Notes 6, Eudora, New Yahoo, Old GMail, New GMail, Live Mail]
197
+ support_level: 77%
198
+ support: 3
199
+ top:
200
+ unsupported_in: [Outlook 07, Notes 6, Eudora, New Yahoo, Old GMail, New GMail, Live Mail]
201
+ support_level: 77%
202
+ support: 3
203
+ background-position:
204
+ unsupported_in: [Outlook 07, Notes 6, Eudora, Old Yahoo, Old GMail, New GMail, Live Mail, Hotmail]
205
+ support_level: 77%
206
+ support: 3
207
+ border-spacing:
208
+ unsupported_in: [Outlook 03, Outlook 07, Windows Mail, Entourage 2004, AOL 10, Notes 6, Eudora, Live Mail, Hotmail]
209
+ support_level: 46%
210
+ support: 3
211
+ bottom:
212
+ unsupported_in: [Outlook 07, AOL 9, Notes 6, Eudora, New Yahoo, Old GMail, New GMail, Live Mail]
213
+ support_level: 69%
214
+ support: 3
215
+ empty-cells:
216
+ unsupported_in: [Outlook 03, Outlook 07, Windows Mail, Entourage 2004, AOL 9, AOL 10, Notes 6, Eudora, Hotmail]
217
+ support_level: 38%
218
+ support: 3
219
+ position:
220
+ unsupported_in: [Outlook 07, Notes 6, Eudora, Old Yahoo, New Yahoo, Old GMail, New GMail, Live Mail, Hotmail, Mobile Me]
221
+ support_level: 77%
222
+ support: 3
223
+ caption-side:
224
+ unsupported_in: [Outlook 03, Outlook 07, Windows Mail, Mac Mail, Entourage 2004, Entourage 2008, AOL 9, AOL 10, AOL Desktop for Mac, Notes 6, Eudora, New Yahoo, Hotmail]
225
+ support_level: 15%
226
+ support: 3
227
+ opacity:
228
+ unsupported_in: [Outlook 03, Outlook 07, Windows Mail, Entourage 2004, Notes 6, Eudora, New Yahoo, Old GMail, New GMail, Live Mail, Hotmail]
229
+ support_level: 54%
230
+ support: 3
metadata ADDED
@@ -0,0 +1,94 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: premailer
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.5.2
5
+ platform: ruby
6
+ authors:
7
+ - Alex Dunae
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-11-27 00:00:00 -08:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: hpricot
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0.6"
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: css_parser
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 0.9.0
34
+ version:
35
+ - !ruby/object:Gem::Dependency
36
+ name: text-reform
37
+ type: :runtime
38
+ version_requirement:
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: 0.2.0
44
+ version:
45
+ description: Improve the rendering of HTML emails by making CSS inline, converting links and warning about unsupported code.
46
+ email: code@dunae.ca
47
+ executables: []
48
+
49
+ extensions: []
50
+
51
+ extra_rdoc_files: []
52
+
53
+ files:
54
+ - README.rdoc
55
+ - CHANGELOG.rdoc
56
+ - LICENSE.rdoc
57
+ - lib/premailer.rb
58
+ - lib/premailer/premailer.rb
59
+ - lib/premailer/html_to_plain_text.rb
60
+ - misc/client_support.yaml
61
+ has_rdoc: true
62
+ homepage: http://premailer.dialect.ca/
63
+ licenses: []
64
+
65
+ post_install_message:
66
+ rdoc_options:
67
+ - --all
68
+ - --inline-source
69
+ - --line-numbers
70
+ - --charset
71
+ - utf-8
72
+ require_paths:
73
+ - lib
74
+ required_ruby_version: !ruby/object:Gem::Requirement
75
+ requirements:
76
+ - - ">="
77
+ - !ruby/object:Gem::Version
78
+ version: "0"
79
+ version:
80
+ required_rubygems_version: !ruby/object:Gem::Requirement
81
+ requirements:
82
+ - - ">="
83
+ - !ruby/object:Gem::Version
84
+ version: "0"
85
+ version:
86
+ requirements: []
87
+
88
+ rubyforge_project:
89
+ rubygems_version: 1.3.5
90
+ signing_key:
91
+ specification_version: 3
92
+ summary: Preflight for HTML e-mail.
93
+ test_files: []
94
+