html2email 0.1.2

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.
Files changed (38) hide show
  1. data/.gitignore +1 -0
  2. data/LICENSE +23 -0
  3. data/README.markdown +110 -0
  4. data/Rakefile +57 -0
  5. data/bin/html2email +5 -0
  6. data/html2email.gemspec +47 -0
  7. data/lib/html2email/context.rb +42 -0
  8. data/lib/html2email/html_email.rb +59 -0
  9. data/lib/html2email/html_mailer.rb +49 -0
  10. data/lib/html2email/vendor/premailer/.gitignore +1 -0
  11. data/lib/html2email/vendor/premailer/CHANGELOG.rdoc +62 -0
  12. data/lib/html2email/vendor/premailer/LICENSE.rdoc +42 -0
  13. data/lib/html2email/vendor/premailer/README.rdoc +66 -0
  14. data/lib/html2email/vendor/premailer/bin/premailer +72 -0
  15. data/lib/html2email/vendor/premailer/bin/trollop.rb +739 -0
  16. data/lib/html2email/vendor/premailer/init.rb +1 -0
  17. data/lib/html2email/vendor/premailer/lib/premailer/html_to_plain_text.rb +81 -0
  18. data/lib/html2email/vendor/premailer/lib/premailer/premailer.rb +464 -0
  19. data/lib/html2email/vendor/premailer/lib/premailer.rb +9 -0
  20. data/lib/html2email/vendor/premailer/misc/client_support.yaml +230 -0
  21. data/lib/html2email/vendor/premailer/premailer.gemspec +20 -0
  22. data/lib/html2email/vendor/premailer/rakefile.rb +42 -0
  23. data/lib/html2email/vendor/premailer/tests/files/base.html +145 -0
  24. data/lib/html2email/vendor/premailer/tests/files/contact_bg.png +0 -0
  25. data/lib/html2email/vendor/premailer/tests/files/dialect.png +0 -0
  26. data/lib/html2email/vendor/premailer/tests/files/dots_end.png +0 -0
  27. data/lib/html2email/vendor/premailer/tests/files/dots_h.gif +0 -0
  28. data/lib/html2email/vendor/premailer/tests/files/import.css +13 -0
  29. data/lib/html2email/vendor/premailer/tests/files/inc/2009-placeholder.png +0 -0
  30. data/lib/html2email/vendor/premailer/tests/files/noimport.css +13 -0
  31. data/lib/html2email/vendor/premailer/tests/files/styles.css +102 -0
  32. data/lib/html2email/vendor/premailer/tests/test_helper.rb +2 -0
  33. data/lib/html2email/vendor/premailer/tests/test_html_to_plain_text.rb +73 -0
  34. data/lib/html2email/vendor/premailer/tests/test_link_resolver.rb +49 -0
  35. data/lib/html2email/vendor/premailer/tests/test_premailer.rb +124 -0
  36. data/lib/html2email.rb +110 -0
  37. data/spec/html2email.spec.rb +9 -0
  38. metadata +176 -0
@@ -0,0 +1 @@
1
+ require 'premailer'
@@ -0,0 +1,81 @@
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 = 65, 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
+ # decode HTML entities
19
+ he = HTMLEntities.new
20
+ txt = he.decode(txt)
21
+
22
+ # handle headings (H1-H6)
23
+ txt.gsub!(/(<\/h[1-6]>)/i, "\n\\1") # move closing tags to new lines
24
+ txt.gsub!(/[\s]*<h([1-6]+)[^>]*>[\s]*(.*)[\s]*<\/h[1-6]+>/i) do |s|
25
+ hlevel = $1.to_i
26
+
27
+ htext = $2
28
+ htext.gsub!(/<br[\s]*\/?>/i, "\n") # handle <br>s
29
+ htext.gsub!(/<\/?[^>]*>/i, '') # strip tags
30
+
31
+ # determine maximum line length
32
+ hlength = 0
33
+ htext.each { |l| llength = l.strip.length; hlength = llength if llength > hlength }
34
+ hlength = line_length if hlength > line_length
35
+
36
+ case hlevel
37
+ when 1 # H1, asterisks above and below
38
+ htext = ('*' * hlength) + "\n" + htext + "\n" + ('*' * hlength)
39
+ when 2 # H1, dashes above and below
40
+ htext = ('-' * hlength) + "\n" + htext + "\n" + ('-' * hlength)
41
+ else # H3-H6, dashes below
42
+ htext = htext + "\n" + ('-' * hlength)
43
+ end
44
+
45
+ "\n\n" + htext + "\n\n"
46
+ end
47
+
48
+ # links
49
+ txt.gsub!(/<a.*href=\"([^\"]*)\"[^>]*>(.*)<\/a>/i) do |s|
50
+ $2.strip + ' ( ' + $1.strip + ' )'
51
+ end
52
+
53
+ # lists -- TODO: should handle ordered lists
54
+ txt.gsub!(/[\s]*(<li[^>]*>)[\s]*/i, '* ')
55
+ # list not followed by a newline
56
+ txt.gsub!(/<\/li>[\s]*(?![\n])/i, "\n")
57
+
58
+ # paragraphs and line breaks
59
+ txt.gsub!(/<\/p>/i, "\n\n")
60
+ txt.gsub!(/<br[\/ ]*>/i, "\n")
61
+
62
+ # strip remaining tags
63
+ txt.gsub!(/<\/?[^>]*>/, '')
64
+
65
+ # wrap text
66
+ txt = r.format(('[' * line_length), txt)
67
+
68
+ # remove linefeeds (\r\n and \r -> \n)
69
+ txt.gsub!(/\r\n?/, "\n")
70
+
71
+ # strip extra spaces
72
+ txt.gsub!(/\302\240+/, " ") # non-breaking spaces -> spaces
73
+ txt.gsub!(/\n[ \t]+/, "\n") # space at start of lines
74
+ txt.gsub!(/[ \t]+\n/, "\n") # space at end of lines
75
+
76
+ # no more than two consecutive newlines
77
+ txt.gsub!(/[\n]{3,}/, "\n\n")
78
+
79
+ txt.strip
80
+ end
81
+ end
@@ -0,0 +1,464 @@
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.5'
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
+ # list of CSS attributes that can be rendered as HTML attributes
43
+ #
44
+ # TODO: too much repetition
45
+ # TODO: background=""
46
+ RELATED_ATTRIBUTES = {
47
+ 'h1' => {'text-align' => 'align'},
48
+ 'h2' => {'text-align' => 'align'},
49
+ 'h3' => {'text-align' => 'align'},
50
+ 'h4' => {'text-align' => 'align'},
51
+ 'h5' => {'text-align' => 'align'},
52
+ 'h6' => {'text-align' => 'align'},
53
+ 'p' => {'text-align' => 'align'},
54
+ 'div' => {'text-align' => 'align'},
55
+ 'blockquote' => {'text-align' => 'align'},
56
+ 'body' => {'background-color' => 'bgcolor'},
57
+ 'table' => {'background-color' => 'bgcolor'},
58
+ 'tr' => {'text-align' => 'align', 'background-color' => 'bgcolor'},
59
+ 'th' => {'text-align' => 'align', 'background-color' => 'bgcolor', 'vertical-align' => 'valign'},
60
+ 'td' => {'text-align' => 'align', 'background-color' => 'bgcolor', 'vertical-align' => 'valign'},
61
+ 'img' => {'float' => 'align'}
62
+ }
63
+
64
+ # URI of the HTML file used
65
+ attr_reader :html_file
66
+
67
+ # processed HTML document (Hpricot)
68
+ attr_reader :processed_doc
69
+
70
+ # source HTML document (Hpricot)
71
+ attr_reader :doc
72
+
73
+ module Warnings
74
+ NONE = 0
75
+ SAFE = 1
76
+ POOR = 2
77
+ RISKY = 3
78
+ end
79
+ include Warnings
80
+
81
+ WARN_LABEL = %w(NONE SAFE POOR RISKY)
82
+
83
+ # Create a new Premailer object.
84
+ #
85
+ # +path+ is the path to the HTML file to process. Can be either the URL of a
86
+ # remote file or a local path.
87
+ #
88
+ # ==== Options
89
+ # [+line_length+] Line length used by to_plain_text. Boolean, default is 65.
90
+ # [+warn_level+] What level of CSS compatibility warnings to show (see Warnings).
91
+ # [+link_query_string+] A string to append to every <a href=""> link. Do not include the initial +?+.
92
+ # [+base_url+] Used to calculate absolute URLs for local files.
93
+ # [+css+] Manually specify a CSS stylesheet.
94
+ # [+css_to_attributes+] Copy related CSS attributes into HTML attributes (e.g. +background-color+ to +bgcolor+)
95
+ def initialize(path, options = {})
96
+ @options = {:warn_level => Warnings::SAFE,
97
+ :line_length => 65,
98
+ :link_query_string => nil,
99
+ :base_url => nil,
100
+ :remove_classes => false,
101
+ :css => [],
102
+ :css_to_attributes => true}.merge(options)
103
+ @html_file = path
104
+
105
+ @is_local_file = local_uri?(path)
106
+
107
+ @css_files = @options[:css]
108
+
109
+ @css_warnings = []
110
+
111
+ @css_parser = CssParser::Parser.new({:absolute_paths => true,
112
+ :import => true,
113
+ :io_exceptions => false
114
+ })
115
+
116
+ @doc, @html_charset = load_html(@html_file)
117
+ @processed_doc = @doc
118
+
119
+ if @is_local_file and @options[:base_url]
120
+ @processed_doc = convert_inline_links(@processed_doc, @options[:base_url])
121
+ elsif not @is_local_file
122
+ @processed_doc = convert_inline_links(@processed_doc, @html_file)
123
+ end
124
+ load_css_from_options!
125
+ load_css_from_html!
126
+ end
127
+
128
+ def local_uri?(uri)
129
+ if uri =~ /^(http|https|ftp)\:\/\//i
130
+ return false
131
+ else
132
+ return true
133
+ end
134
+ end
135
+
136
+ # Array containing a hash of CSS warnings.
137
+ def warnings
138
+ return [] if @options[:warn_level] == Warnings::NONE
139
+ @css_warnings = check_client_support if @css_warnings.empty?
140
+ @css_warnings
141
+ end
142
+
143
+ # Returns the original HTML as a string.
144
+ def to_s
145
+ @doc.to_html
146
+ end
147
+
148
+ # Converts the HTML document to a format suitable for plain-text e-mail.
149
+ #
150
+ # Returns a string.
151
+ def to_plain_text
152
+ html_src = ''
153
+ begin
154
+ html_src = @doc.search("body").innerHTML
155
+ rescue
156
+ html_src = @doc.to_html
157
+ end
158
+ convert_to_text(html_src, @options[:line_length], @html_charset)
159
+ end
160
+
161
+ # Merge CSS into the HTML document.
162
+ #
163
+ # Returns a string.
164
+ def to_inline_css
165
+ doc = @processed_doc
166
+ unmergable_rules = CssParser::Parser.new
167
+
168
+ # Give all styles already in style attributes a specificity of 1000
169
+ # per http://www.w3.org/TR/CSS21/cascade.html#specificity
170
+ doc.search("*[@style]").each do |el|
171
+ el['style'] = '[SPEC=1000[' + el.attributes['style'] + ']]'
172
+ end
173
+
174
+ # Iterate through the rules and merge them into the HTML
175
+ @css_parser.each_selector(:all) do |selector, declaration, specificity|
176
+ # Save un-mergable rules separately
177
+ selector.gsub!(/:link([\s]|$)+/i, '')
178
+
179
+ # Convert element names to lower case
180
+ selector.gsub!(/([\s]|^)([\w]+)/) {|m| $1.to_s + $2.to_s.downcase }
181
+
182
+ if selector =~ RE_UNMERGABLE_SELECTORS
183
+ unmergable_rules.add_rule_set!(RuleSet.new(selector, declaration))
184
+ else
185
+
186
+ doc.search(selector) do |el|
187
+ if el.elem?
188
+ # Add a style attribute or append to the existing one
189
+ block = "[SPEC=#{specificity}[#{declaration}]]"
190
+ el['style'] = (el.attributes['style'] ||= '') + ' ' + block
191
+ end
192
+ end
193
+ end
194
+ end
195
+
196
+ # Read STYLE attributes and perform folding
197
+ doc.search("*[@style]").each do |el|
198
+ style = el.attributes['style'].to_s
199
+
200
+ declarations = []
201
+
202
+ style.scan(/\[SPEC\=([\d]+)\[(.[^\]\]]*)\]\]/).each do |declaration|
203
+ rs = RuleSet.new(nil, declaration[1].to_s, declaration[0].to_i)
204
+ declarations << rs
205
+ end
206
+
207
+ # Perform style folding
208
+ merged = CssParser.merge(declarations)
209
+ merged.expand_shorthand!
210
+
211
+ #if @options[:prefer_cellpadding] and (el.name == 'td' or el.name == 'th') and el['cellpadding'].nil?
212
+ # if cellpadding = equivalent_cellpadding(merged)
213
+ # el['cellpadding'] = cellpadding
214
+ # merged['padding-left'] = nil
215
+ # merged['padding-right'] = nil
216
+ # merged['padding-top'] = nil
217
+ # merged['padding-bottom'] = nil
218
+ # end
219
+ #end
220
+
221
+ # Duplicate CSS attributes as HTML attributes
222
+ if RELATED_ATTRIBUTES.has_key?(el.name)
223
+ RELATED_ATTRIBUTES[el.name].each do |css_att, html_att|
224
+ el[html_att] = merged[css_att].gsub(/;$/, '').strip if el[html_att].nil? and not merged[css_att].empty?
225
+ end
226
+ end
227
+
228
+ merged.create_dimensions_shorthand!
229
+
230
+ # write the inline STYLE attribute
231
+ el['style'] = Premailer.escape_string(merged.declarations_to_s)
232
+ end
233
+
234
+ doc = write_unmergable_css_rules(doc, unmergable_rules)
235
+
236
+ doc.search('*').remove_class if @options[:remove_classes]
237
+
238
+ @processed_doc = doc
239
+
240
+ doc.to_html
241
+ end
242
+
243
+
244
+ protected
245
+ # Load the HTML file and convert it into an Hpricot document.
246
+ #
247
+ # Returns an Hpricot document and a string with the HTML file's character set.
248
+ def load_html(path) # :nodoc:
249
+ if @is_local_file
250
+ Hpricot(File.open(path, "r") {|f| f.read })
251
+ else
252
+ Hpricot(open(path))
253
+ end
254
+ end
255
+
256
+ def load_css_from_local_file!(path)
257
+ css_block = Tilt[path] ? Tilt.new(path).render : File.read(path)
258
+ @css_parser.add_block!(css_block, {:base_uri => @html_file})
259
+ rescue
260
+ end
261
+
262
+ def load_css_from_options! # :nodoc:
263
+ @css_files.each do |css_file|
264
+ if local_uri?(css_file)
265
+ load_css_from_local_file!(css_file)
266
+ else
267
+ @css_parser.load_uri!(css_file)
268
+ end
269
+ end
270
+ end
271
+
272
+ # Load CSS included in <tt>style</tt> and <tt>link</tt> tags from an HTML document.
273
+ def load_css_from_html! # :nodoc:
274
+ if tags = @doc.search("link[@rel='stylesheet'], style")
275
+ tags.each do |tag|
276
+
277
+ if tag.to_s.strip =~ /^\<link/i and tag.attributes['href'] and media_type_ok?(tag.attributes['media'])
278
+
279
+ link_uri = Premailer.resolve_link(tag.attributes['href'].to_s, @html_file)
280
+ if @is_local_file
281
+ load_css_from_local_file!(link_uri)
282
+ else
283
+ @css_parser.load_uri!(link_uri)
284
+ end
285
+
286
+ elsif tag.to_s.strip =~ /^\<style/i
287
+ @css_parser.add_block!(tag.innerHTML, :base_uri => URI.parse(@html_file))
288
+ end
289
+ end
290
+ tags.remove
291
+ end
292
+ end
293
+
294
+ def media_type_ok?(media_types) # :nodoc:
295
+ return true if media_types.nil? or media_types.empty?
296
+ return media_types.split(/[\s]+|,/).any? { |media_type| media_type.strip =~ /screen|handheld|all/i }
297
+ rescue
298
+ return true
299
+ end
300
+
301
+ # Create a <tt>style</tt> element with un-mergable rules (e.g. <tt>:hover</tt>)
302
+ # and write it into the <tt>body</tt>.
303
+ #
304
+ # <tt>doc</tt> is an Hpricot document and <tt>unmergable_css_rules</tt> is a Css::RuleSet.
305
+ #
306
+ # Returns an Hpricot document.
307
+ def write_unmergable_css_rules(doc, unmergable_rules) # :nodoc:
308
+ styles = ''
309
+ unmergable_rules.each_selector(:all, :force_important => true) do |selector, declarations, specificity|
310
+ styles += "#{selector} { #{declarations} }\n"
311
+ end
312
+
313
+ unless styles.empty?
314
+ style_tag = "\n<style type=\"text/css\">\n#{styles}</style>\n"
315
+ doc.search("head").append(style_tag)
316
+ end
317
+ doc
318
+ end
319
+
320
+ # Convert relative links to absolute links.
321
+ #
322
+ # Processes <tt>href</tt> <tt>src</tt> and <tt>background</tt> attributes
323
+ # as well as CSS <tt>url()</tt> declarations found in inline <tt>style</tt> attributes.
324
+ #
325
+ # <tt>doc</tt> is an Hpricot document and <tt>base_uri</tt> is either a string or a URI.
326
+ #
327
+ # Returns an Hpricot document.
328
+ def convert_inline_links(doc, base_uri) # :nodoc:
329
+ base_uri = URI.parse(base_uri) unless base_uri.kind_of?(URI)
330
+
331
+ append_qs = @options[:link_query_string] ||= ''
332
+
333
+ ['href', 'src', 'background'].each do |attribute|
334
+ tags = doc.search("*[@#{attribute}]")
335
+
336
+ next if tags.empty?
337
+
338
+ tags.each do |tag|
339
+ # skip links that look like they have merge tags
340
+ # and mailto, ftp, etc...
341
+ if tag.attributes[attribute] =~ /^(\{|\[|<|\#|mailto:|ftp:|gopher:)/i
342
+ next
343
+ end
344
+
345
+ if tag.attributes[attribute] =~ /^http/i
346
+ begin
347
+ merged = URI.parse(tag.attributes[attribute])
348
+ rescue; next; end
349
+ else
350
+ begin
351
+ merged = Premailer.resolve_link(tag.attributes[attribute].to_s, base_uri)
352
+ rescue
353
+ begin
354
+ merged = Premailer.resolve_link(URI.escape(tag.attributes[attribute].to_s), base_uri)
355
+ rescue; end
356
+ end
357
+ end
358
+
359
+ # make sure 'merged' is a URI
360
+ merged = URI.parse(merged.to_s) unless merged.kind_of?(URI)
361
+
362
+ # only append a querystring to <a> tags
363
+ if tag.name =~ /^a$/i and not append_qs.empty?
364
+ if merged.query
365
+ merged.query = merged.query + '&' + append_qs
366
+ else
367
+ merged.query = append_qs
368
+ end
369
+ end
370
+ tag[attribute] = merged.to_s
371
+
372
+ end # end of each tag
373
+ end # end of each attrs
374
+
375
+ doc.search("*[@style]").each do |el|
376
+ el['style'] = CssParser.convert_uris(el.attributes['style'].to_s, base_uri)
377
+ end
378
+ doc
379
+ end
380
+
381
+ def self.escape_string(str) # :nodoc:
382
+ str.gsub(/"/, "'")
383
+ end
384
+
385
+ def self.resolve_link(path, base_path) # :nodoc:
386
+ path.strip!
387
+ resolved = nil
388
+ if base_path.kind_of?(URI)
389
+ resolved = base_path.merge(path)
390
+ return Premailer.canonicalize(resolved)
391
+ elsif base_path.kind_of?(String) and base_path =~ /^(http[s]?|ftp):\/\//i
392
+ resolved = URI.parse(base_path)
393
+ resolved = resolved.merge(path)
394
+ return Premailer.canonicalize(resolved)
395
+ else
396
+
397
+ return File.expand_path(path, File.dirname(base_path))
398
+ end
399
+ end
400
+
401
+ # from http://www.ruby-forum.com/topic/140101
402
+ def self.canonicalize(uri) # :nodoc:
403
+ u = uri.kind_of?(URI) ? uri : URI.parse(uri.to_s)
404
+ u.normalize!
405
+ newpath = u.path
406
+ while newpath.gsub!(%r{([^/]+)/\.\./?}) { |match|
407
+ $1 == '..' ? match : ''
408
+ } do end
409
+ newpath = newpath.gsub(%r{/\./}, '/').sub(%r{/\.\z}, '/')
410
+ u.path = newpath
411
+ u.to_s
412
+ end
413
+
414
+ # Check <tt>CLIENT_SUPPORT_FILE</tt> for any CSS warnings
415
+ def check_client_support # :nodoc:
416
+ @client_support = @client_support ||= YAML::load(File.open(CLIENT_SUPPORT_FILE))
417
+
418
+ warnings = []
419
+ properties = []
420
+
421
+ # Get a list off CSS properties
422
+ @processed_doc.search("*[@style]").each do |el|
423
+ style_url = el.attributes['style'].gsub(/([\w\-]+)[\s]*\:/i) do |s|
424
+ properties.push($1)
425
+ end
426
+ end
427
+
428
+ properties.uniq!
429
+
430
+ property_support = @client_support['css_properties']
431
+ properties.each do |prop|
432
+ if property_support.include?(prop) and
433
+ property_support[prop].include?('support') and
434
+ property_support[prop]['support'] >= @options[:warn_level]
435
+ warnings.push({:message => "#{prop} CSS property",
436
+ :level => WARN_LABEL[property_support[prop]['support']],
437
+ :clients => property_support[prop]['unsupported_in'].join(', ')})
438
+ end
439
+ end
440
+
441
+ @client_support['attributes'].each do |attribute, data|
442
+ next unless data['support'] >= @options[:warn_level]
443
+ if @doc.search("*[@#{attribute}]").length > 0
444
+ warnings.push({:message => "#{attribute} HTML attribute",
445
+ :level => WARN_LABEL[property_support[prop]['support']],
446
+ :clients => property_support[prop]['unsupported_in'].join(', ')})
447
+ end
448
+ end
449
+
450
+ @client_support['elements'].each do |element, data|
451
+ next unless data['support'] >= @options[:warn_level]
452
+ if @doc.search("element").length > 0
453
+ warnings.push({:message => "#{element} HTML element",
454
+ :level => WARN_LABEL[property_support[prop]['support']],
455
+ :clients => property_support[prop]['unsupported_in'].join(', ')})
456
+ end
457
+ end
458
+
459
+ return warnings
460
+ end
461
+ end
462
+
463
+
464
+
@@ -0,0 +1,9 @@
1
+ # Premailer by Alex Dunae (dunae.ca, e-mail 'code' at the same domain), 2008-09
2
+ require 'yaml'
3
+ require 'open-uri'
4
+ require 'hpricot'
5
+ require 'css_parser'
6
+ require 'tilt'
7
+
8
+ require File.dirname(__FILE__) + "/premailer/html_to_plain_text"
9
+ require File.dirname(__FILE__) + "/premailer/premailer"