premailer 1.5.4 → 1.5.6

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.
@@ -20,7 +20,6 @@ script is my solution.
20
20
 
21
21
  Download the Premailer gem from GemCutter.
22
22
 
23
- gem sources -a http://gemcutter.org
24
23
  sudo gem install premailer
25
24
 
26
25
  === Example
@@ -48,19 +47,20 @@ Contributions are most welcome. Premailer was rotting away in a private SVN rep
48
47
  A few areas that are particularly in need of love:
49
48
  * Testing suite
50
49
  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
50
  * Create a binary file for easing command line use, allowing the output to be piped in *nix systems
53
- * Ruby 1.9 testing
54
51
  * Test with Rails
55
52
  * Move un-repeated background images defined in CSS to <tt><td background=""></tt> for Outlook
56
53
  * Correctly parse http://www.webstandards.org/files/acid2/test.html
57
54
 
58
55
  === Credits and code
59
56
 
60
- Premailer is written in Ruby.
57
+ Thanks to {all the wonderful contributors}[http://github.com/alexdunae/premailer/contributors] for their updates.
58
+
59
+ Thanks to {Greenhood + Company}[http://www.greenhood.com/] for sponsoring some of the 1.5.6 updates,
60
+ and to {Campaign Monitor}[http://www.campaignmonitor.com] for supporting the web interface.
61
61
 
62
62
  The web interface can be found at http://premailer.dialect.ca .
63
63
 
64
64
  The source code can be found at http://github.com/alexdunae/premailer .
65
65
 
66
- Written by Alex Dunae (dunae.ca, e-mail 'code' at the same domain), 2008-2009.
66
+ Written by Alex Dunae (dunae.ca, e-mail 'code' at the same domain), 2008-2010.
@@ -1,7 +1,8 @@
1
1
  #!/usr/bin/env ruby
2
2
  #
3
3
  # = Premailer
4
- require 'trollop'
4
+ require 'rubygems'
5
+ require File.join(File.dirname(__FILE__), 'trollop')
5
6
  require File.join(File.dirname(__FILE__), '../lib/premailer')
6
7
 
7
8
  opts = Trollop::options do
@@ -23,7 +24,9 @@ EOS
23
24
  opt :query_string, "Query string to append to links", :type => String, :short => 'q'
24
25
  opt :line_length, "Length of lines when creating plaintext version", :type => :int, :default => 65, :short => 'l'
25
26
  opt :remove_classes, "Remove classes from the HTML document?", :default => false
27
+ opt :css, "Manually specify css stylesheets", :type => String, :multi => true
26
28
  opt :verbose, '', :default => false, :short => 'v'
29
+ opt :io_exceptions, "Abort on I/O errors loading style resources", :default => false, :short => 'e'
27
30
  end
28
31
 
29
32
  inputfile = ARGV.shift
@@ -38,7 +41,10 @@ premailer_opts = {
38
41
  :query_string => opts[:query_string],
39
42
  :show_warnings => opts[:show_warnings] ? Premailer::Warnings::SAFE : Premailer::Warnings::NONE,
40
43
  :line_length => opts[:line_length],
41
- :remove_classes => opts[:remove_classes]
44
+ :remove_classes => opts[:remove_classes],
45
+ :css => opts[:css],
46
+ :verbose => opts[:verbose],
47
+ :io_exceptions => opts[:io_exceptions],
42
48
  }
43
49
 
44
50
  premailer = Premailer.new(inputfile, premailer_opts)
@@ -1,9 +1,7 @@
1
- # Premailer by Alex Dunae (dunae.ca, e-mail 'code' at the same domain), 2008-09
2
-
3
1
  require 'yaml'
4
2
  require 'open-uri'
5
- require 'hpricot'
3
+ require 'cgi'
4
+ require 'nokogiri'
6
5
  require 'css_parser'
7
-
8
- require File.dirname(__FILE__) + "/premailer/html_to_plain_text"
9
- require File.dirname(__FILE__) + "/premailer/premailer"
6
+ require 'premailer/html_to_plain_text'
7
+ require 'premailer/premailer'
@@ -1,4 +1,4 @@
1
- require 'text/reform'
1
+ # coding: utf-8
2
2
  require 'htmlentities'
3
3
 
4
4
  # Support functions for Premailer
@@ -9,9 +9,9 @@ module HtmlToPlainText
9
9
  # TODO:
10
10
  # - add support for DL, OL
11
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)
12
+ #r = Text::Reform.new(:trim => true,
13
+ # :squeeze => false,
14
+ # :break => Text::Reform.break_wrap)
15
15
 
16
16
  txt = html
17
17
 
@@ -20,22 +20,29 @@ module HtmlToPlainText
20
20
  txt = he.decode(txt)
21
21
 
22
22
  # handle headings (H1-H6)
23
- txt.gsub!(/[ \t]*<h([0-9]+)[^>]*>(.*)<\/h[0-9]+>/i) do |s|
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|
24
25
  hlevel = $1.to_i
25
- # cleanup text inside of headings
26
- htext = $2.gsub(/<\/?[^>]*>/i, '').strip
27
- hlength = (htext.length > line_length ?
28
- line_length :
29
- htext.length)
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_line { |l| llength = l.strip.length; hlength = llength if llength > hlength }
34
+ hlength = line_length if hlength > line_length
30
35
 
31
36
  case hlevel
32
37
  when 1 # H1, asterisks above and below
33
- ('*' * hlength) + "\n" + htext + "\n" + ('*' * hlength) + "\n"
38
+ htext = ('*' * hlength) + "\n" + htext + "\n" + ('*' * hlength)
34
39
  when 2 # H1, dashes above and below
35
- ('-' * hlength) + "\n" + htext + "\n" + ('-' * hlength) + "\n"
40
+ htext = ('-' * hlength) + "\n" + htext + "\n" + ('-' * hlength)
36
41
  else # H3-H6, dashes below
37
- htext + "\n" + ('-' * htext.length) + "\n"
42
+ htext = htext + "\n" + ('-' * hlength)
38
43
  end
44
+
45
+ "\n\n" + htext + "\n\n"
39
46
  end
40
47
 
41
48
  # links
@@ -56,7 +63,7 @@ module HtmlToPlainText
56
63
  txt.gsub!(/<\/?[^>]*>/, '')
57
64
 
58
65
  # wrap text
59
- txt = r.format(('[' * line_length), txt)
66
+ #txt = r.format(('[' * line_length), txt)
60
67
 
61
68
  # remove linefeeds (\r\n and \r -> \n)
62
69
  txt.gsub!(/\r\n?/, "\n")
@@ -33,21 +33,48 @@ class Premailer
33
33
  include HtmlToPlainText
34
34
  include CssParser
35
35
 
36
- VERSION = '1.5.4'
36
+ VERSION = '1.5.6'
37
37
 
38
38
  CLIENT_SUPPORT_FILE = File.dirname(__FILE__) + '/../../misc/client_support.yaml'
39
39
 
40
40
  RE_UNMERGABLE_SELECTORS = /(\:(visited|active|hover|focus|after|before|selection|target|first\-(line|letter))|^\@)/i
41
41
 
42
- # should also exclude :first-letter, etc...
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
+ }
43
63
 
44
64
  # URI of the HTML file used
45
65
  attr_reader :html_file
46
66
 
47
- # processed HTML document (Hpricot)
67
+ # base URL used to resolve links
68
+ attr_reader :base_url
69
+
70
+ # base directory used to resolve links for local files
71
+ attr_reader :base_dir
72
+
73
+
74
+ # processed HTML document (Nokogiri)
48
75
  attr_reader :processed_doc
49
76
 
50
- # source HTML document (Hpricot)
77
+ # source HTML document (Nokogiri)
51
78
  attr_reader :doc
52
79
 
53
80
  module Warnings
@@ -62,42 +89,61 @@ class Premailer
62
89
 
63
90
  # Create a new Premailer object.
64
91
  #
65
- # +path+ is the path to the HTML file to process. Can be either the URL of a
66
- # remote file or a local path.
92
+ # +html+ is the HTML data to process. It can be either an IO object, the URL of a
93
+ # remote file, a local path or a raw HTML string. If passing an HTML string you
94
+ # must set the +:with_html_string+ option to +true+.
67
95
  #
68
96
  # ==== Options
69
97
  # [+line_length+] Line length used by to_plain_text. Boolean, default is 65.
70
98
  # [+warn_level+] What level of CSS compatibility warnings to show (see Warnings).
71
99
  # [+link_query_string+] A string to append to every <a href=""> link. Do not include the initial +?+.
72
100
  # [+base_url+] Used to calculate absolute URLs for local files.
73
- def initialize(path, options = {})
101
+ # [+css+] Manually specify a CSS stylesheet.
102
+ # [+css_to_attributes+] Copy related CSS attributes into HTML attributes (e.g. +background-color+ to +bgcolor+)
103
+ # [+with_html_string+] Whether the +html+ param should be treated as a raw string.
104
+ def initialize(html, options = {})
74
105
  @options = {:warn_level => Warnings::SAFE,
75
106
  :line_length => 65,
76
107
  :link_query_string => nil,
77
108
  :base_url => nil,
78
- :remove_classes => false}.merge(options)
79
- @html_file = path
80
-
81
- @is_local_file = true
82
- if path =~ /^(http|https|ftp)\:\/\//i
83
- @is_local_file = false
84
- end
109
+ :remove_classes => false,
110
+ :css => [],
111
+ :css_to_attributes => true,
112
+ :with_html_string => false,
113
+ :verbose => false,
114
+ :io_exceptions => false}.merge(options)
115
+
116
+ @html_file = html
117
+ @is_local_file = @options[:with_html_string] || Premailer.local_data?(html)
118
+
119
+ @css_files = @options[:css]
85
120
 
86
121
  @css_warnings = []
87
122
 
88
- @css_parser = CssParser::Parser.new({:absolute_paths => true,
89
- :import => true,
90
- :io_exceptions => false
91
- })
123
+ @base_url = nil
124
+ @base_dir = nil
125
+
126
+ if @options[:base_url]
127
+ @base_url = URI.parse(@options.delete[:base_url])
128
+ elsif not @is_local_file
129
+ @base_url = URI.parse(@html_file)
130
+ end
131
+
132
+ @css_parser = CssParser::Parser.new({
133
+ :absolute_paths => true,
134
+ :import => true,
135
+ :io_exceptions => @options[:io_exceptions]
136
+ })
92
137
 
93
- @doc, @html_charset = load_html(@html_file)
138
+ @doc = load_html(@html_file)
139
+
140
+ @html_charset = @doc.encoding
94
141
  @processed_doc = @doc
95
-
96
- if @is_local_file and @options[:base_url]
97
- @processed_doc = convert_inline_links(@processed_doc, @options[:base_url])
98
- elsif not @is_local_file
99
- @processed_doc = convert_inline_links(@processed_doc, @html_file)
142
+ @processed_doc = convert_inline_links(@processed_doc, @base_url) if @base_url
143
+ if options[:link_query_string]
144
+ @processed_doc = append_query_string(@processed_doc, options[:link_query_string])
100
145
  end
146
+ load_css_from_options!
101
147
  load_css_from_html!
102
148
  end
103
149
 
@@ -119,7 +165,7 @@ class Premailer
119
165
  def to_plain_text
120
166
  html_src = ''
121
167
  begin
122
- html_src = @doc.search("body").innerHTML
168
+ html_src = @doc.search("body").inner_html
123
169
  rescue
124
170
  html_src = @doc.to_html
125
171
  end
@@ -151,17 +197,17 @@ class Premailer
151
197
  unmergable_rules.add_rule_set!(RuleSet.new(selector, declaration))
152
198
  else
153
199
 
154
- doc.search(selector) do |el|
200
+ doc.css(selector).each do |el|
155
201
  if el.elem?
156
202
  # Add a style attribute or append to the existing one
157
203
  block = "[SPEC=#{specificity}[#{declaration}]]"
158
- el['style'] = (el.attributes['style'] ||= '') + ' ' + block
204
+ el['style'] = (el.attributes['style'].to_s ||= '') + ' ' + block
159
205
  end
160
206
  end
161
207
  end
162
208
  end
163
209
 
164
- # Read <style> attributes and perform folding
210
+ # Read STYLE attributes and perform folding
165
211
  doc.search("*[@style]").each do |el|
166
212
  style = el.attributes['style'].to_s
167
213
 
@@ -172,9 +218,30 @@ class Premailer
172
218
  declarations << rs
173
219
  end
174
220
 
175
- # Perform style folding and save
221
+ # Perform style folding
176
222
  merged = CssParser.merge(declarations)
223
+ merged.expand_shorthand!
224
+
225
+ #if @options[:prefer_cellpadding] and (el.name == 'td' or el.name == 'th') and el['cellpadding'].nil?
226
+ # if cellpadding = equivalent_cellpadding(merged)
227
+ # el['cellpadding'] = cellpadding
228
+ # merged['padding-left'] = nil
229
+ # merged['padding-right'] = nil
230
+ # merged['padding-top'] = nil
231
+ # merged['padding-bottom'] = nil
232
+ # end
233
+ #end
234
+
235
+ # Duplicate CSS attributes as HTML attributes
236
+ if RELATED_ATTRIBUTES.has_key?(el.name)
237
+ RELATED_ATTRIBUTES[el.name].each do |css_att, html_att|
238
+ el[html_att] = merged[css_att].gsub(/;$/, '').strip if el[html_att].nil? and not merged[css_att].empty?
239
+ end
240
+ end
241
+
242
+ merged.create_dimensions_shorthand!
177
243
 
244
+ # write the inline STYLE attribute
178
245
  el['style'] = Premailer.escape_string(merged.declarations_to_s)
179
246
  end
180
247
 
@@ -189,41 +256,67 @@ class Premailer
189
256
 
190
257
 
191
258
  protected
192
- # Load the HTML file and convert it into an Hpricot document.
259
+ # Load the HTML file and convert it into an Nokogiri document.
193
260
  #
194
- # Returns an Hpricot document and a string with the HTML file's character set.
261
+ # Returns an Nokogiri document.
195
262
  def load_html(path) # :nodoc:
196
- if @is_local_file
197
- Hpricot(File.open(path, "r") {|f| f.read })
263
+ if @options[:with_html_string]
264
+ Nokogiri::HTML.parse(path)
265
+ elsif @options[:inline]
266
+ Nokogiri::HTML(path)
198
267
  else
199
- Hpricot(open(path))
268
+ if @is_local_file
269
+ if path.is_a?(IO) || path.is_a?(StringIO)
270
+ Nokogiri::HTML(path.read)
271
+ else
272
+ @base_dir = File.dirname(path)
273
+ Nokogiri::HTML(File.open(path, "r") {|f| f.read })
274
+ end
275
+ else
276
+ Nokogiri::HTML(open(path))
277
+ end
200
278
  end
201
279
  end
202
-
280
+
281
+ def load_css_from_local_file!(path)
282
+ css_block = ''
283
+ begin
284
+ File.open(path, "r") do |file|
285
+ while line = file.gets
286
+ css_block << line
287
+ end
288
+ end
289
+ @css_parser.add_block!(css_block, {:base_uri => @base_url, :base_dir => @base_dir})
290
+ rescue; end
291
+ end
292
+
293
+ def load_css_from_options! # :nodoc:
294
+ @css_files.each do |css_file|
295
+ if Premailer.local_data?(css_file)
296
+ load_css_from_local_file!(css_file)
297
+ else
298
+ @css_parser.load_uri!(css_file)
299
+ end
300
+ end
301
+ end
302
+
203
303
  # Load CSS included in <tt>style</tt> and <tt>link</tt> tags from an HTML document.
204
304
  def load_css_from_html! # :nodoc:
205
305
  if tags = @doc.search("link[@rel='stylesheet'], style")
206
306
  tags.each do |tag|
207
-
208
307
  if tag.to_s.strip =~ /^\<link/i and tag.attributes['href'] and media_type_ok?(tag.attributes['media'])
209
308
 
210
309
  link_uri = Premailer.resolve_link(tag.attributes['href'].to_s, @html_file)
211
- if @is_local_file
212
- css_block = ''
213
- begin
214
- File.open(link_uri, "r") do |file|
215
- while line = file.gets
216
- css_block << line
217
- end
218
- end
219
- @css_parser.add_block!(css_block, {:base_uri => @html_file})
220
- rescue; end
310
+ if Premailer.local_data?(link_uri)
311
+ puts "Loading css from local file: " + link_uri if @options[:verbose]
312
+ load_css_from_local_file!(link_uri)
221
313
  else
314
+ puts "Loading css from uri: " + link_uri if @options[:verbose]
222
315
  @css_parser.load_uri!(link_uri)
223
316
  end
224
317
 
225
- elsif tag.to_s.strip =~ /^\<style/i
226
- @css_parser.add_block!(tag.innerHTML, :base_uri => URI.parse(@html_file))
318
+ elsif tag.to_s.strip =~ /^\<style/i
319
+ @css_parser.add_block!(tag.inner_html, :base_uri => @base_url, :base_dir => @base_dir, :only_media_types => [:screen, :handheld])
227
320
  end
228
321
  end
229
322
  tags.remove
@@ -240,9 +333,9 @@ protected
240
333
  # Create a <tt>style</tt> element with un-mergable rules (e.g. <tt>:hover</tt>)
241
334
  # and write it into the <tt>body</tt>.
242
335
  #
243
- # <tt>doc</tt> is an Hpricot document and <tt>unmergable_css_rules</tt> is a Css::RuleSet.
336
+ # <tt>doc</tt> is an Nokogiri document and <tt>unmergable_css_rules</tt> is a Css::RuleSet.
244
337
  #
245
- # Returns an Hpricot document.
338
+ # Returns an Nokogiri document.
246
339
  def write_unmergable_css_rules(doc, unmergable_rules) # :nodoc:
247
340
  styles = ''
248
341
  unmergable_rules.each_selector(:all, :force_important => true) do |selector, declarations, specificity|
@@ -251,7 +344,26 @@ protected
251
344
 
252
345
  unless styles.empty?
253
346
  style_tag = "\n<style type=\"text/css\">\n#{styles}</style>\n"
254
- doc.search("head").append(style_tag)
347
+ doc.css("head").children.last.after(style_tag)
348
+ end
349
+ doc
350
+ end
351
+
352
+ def append_query_string(doc, qs)
353
+ doc.search('a').each do|el|
354
+ href = el.attributes['href'].to_s
355
+ next if href.nil? or href.empty?
356
+
357
+ href = URI.parse(href)
358
+
359
+ if href.query
360
+ href.query = href.query + '&amp' + qs
361
+ else
362
+ href.query = qs
363
+ end
364
+
365
+ el['href'] = href.to_s
366
+
255
367
  end
256
368
  doc
257
369
  end
@@ -261,13 +373,13 @@ protected
261
373
  # Processes <tt>href</tt> <tt>src</tt> and <tt>background</tt> attributes
262
374
  # as well as CSS <tt>url()</tt> declarations found in inline <tt>style</tt> attributes.
263
375
  #
264
- # <tt>doc</tt> is an Hpricot document and <tt>base_uri</tt> is either a string or a URI.
376
+ # <tt>doc</tt> is an Nokogiri document and <tt>base_uri</tt> is either a string or a URI.
265
377
  #
266
- # Returns an Hpricot document.
378
+ # Returns an Nokogiri document.
267
379
  def convert_inline_links(doc, base_uri) # :nodoc:
268
380
  base_uri = URI.parse(base_uri) unless base_uri.kind_of?(URI)
269
381
 
270
- append_qs = @options[:link_query_string] ||= ''
382
+ append_qs = @options[:link_query_string] || ''
271
383
 
272
384
  ['href', 'src', 'background'].each do |attribute|
273
385
  tags = doc.search("*[@#{attribute}]")
@@ -297,17 +409,7 @@ protected
297
409
 
298
410
  # make sure 'merged' is a URI
299
411
  merged = URI.parse(merged.to_s) unless merged.kind_of?(URI)
300
-
301
- # only append a querystring to <a> tags
302
- if tag.name =~ /^a$/i and not append_qs.empty?
303
- if merged.query
304
- merged.query = merged.query + '&' + append_qs
305
- else
306
- merged.query = append_qs
307
- end
308
- end
309
412
  tag[attribute] = merged.to_s
310
-
311
413
  end # end of each tag
312
414
  end # end of each attrs
313
415
 
@@ -317,13 +419,27 @@ protected
317
419
  doc
318
420
  end
319
421
 
422
+ # here be deprecated methods
423
+ public
424
+
425
+ def local_uri?(uri) # :nodoc:
426
+ warn "[DEPRECATION] `local_uri?` is deprecated. Please use `Premailer.local_data?` instead."
427
+ Premailer.local_data?(uri)
428
+ end
429
+
430
+ # here be instance methods
431
+
320
432
  def self.escape_string(str) # :nodoc:
321
433
  str.gsub(/"/, "'")
322
434
  end
323
435
 
324
436
  def self.resolve_link(path, base_path) # :nodoc:
437
+ path.strip!
325
438
  resolved = nil
326
- if base_path.kind_of?(URI)
439
+ if path =~ /(http[s]?|ftp):\/\//i
440
+ resolved = path
441
+ return Premailer.canonicalize(resolved)
442
+ elsif base_path.kind_of?(URI)
327
443
  resolved = base_path.merge(path)
328
444
  return Premailer.canonicalize(resolved)
329
445
  elsif base_path.kind_of?(String) and base_path =~ /^(http[s]?|ftp):\/\//i
@@ -336,67 +452,80 @@ protected
336
452
  end
337
453
  end
338
454
 
455
+ # Test the passed variable to see if we are in local or remote mode.
456
+ #
457
+ # IO objects return true, as do strings that look like URLs.
458
+ def self.local_data?(data)
459
+ if data.is_a?(IO) || data.is_a?(StringIO)
460
+ return true
461
+ elsif data =~ /^(http|https|ftp)\:\/\//i
462
+ return false
463
+ else
464
+ return true
465
+ end
466
+ end
467
+
339
468
  # from http://www.ruby-forum.com/topic/140101
340
469
  def self.canonicalize(uri) # :nodoc:
341
470
  u = uri.kind_of?(URI) ? uri : URI.parse(uri.to_s)
342
471
  u.normalize!
343
472
  newpath = u.path
344
473
  while newpath.gsub!(%r{([^/]+)/\.\./?}) { |match|
345
- $1 == '..' ? match : ''
346
- } do end
347
- newpath = newpath.gsub(%r{/\./}, '/').sub(%r{/\.\z}, '/')
348
- u.path = newpath
349
- u.to_s
350
- end
474
+ $1 == '..' ? match : ''
475
+ } do end
476
+ newpath = newpath.gsub(%r{/\./}, '/').sub(%r{/\.\z}, '/')
477
+ u.path = newpath
478
+ u.to_s
479
+ end
351
480
 
352
- # Check <tt>CLIENT_SUPPORT_FILE</tt> for any CSS warnings
353
- def check_client_support # :nodoc:
354
- @client_support = @client_support ||= YAML::load(File.open(CLIENT_SUPPORT_FILE))
481
+ # Check <tt>CLIENT_SUPPORT_FILE</tt> for any CSS warnings
482
+ def check_client_support # :nodoc:
483
+ @client_support = @client_support ||= YAML::load(File.open(CLIENT_SUPPORT_FILE))
355
484
 
356
- warnings = []
357
- properties = []
485
+ warnings = []
486
+ properties = []
358
487
 
359
- # Get a list off CSS properties
360
- @processed_doc.search("*[@style]").each do |el|
361
- style_url = el.attributes['style'].gsub(/([\w\-]+)[\s]*\:/i) do |s|
362
- properties.push($1)
488
+ # Get a list off CSS properties
489
+ @processed_doc.search("*[@style]").each do |el|
490
+ style_url = el.attributes['style'].to_s.gsub(/([\w\-]+)[\s]*\:/i) do |s|
491
+ properties.push($1)
492
+ end
363
493
  end
364
- end
365
494
 
366
- properties.uniq!
495
+ properties.uniq!
367
496
 
368
- property_support = @client_support['css_properties']
369
- properties.each do |prop|
370
- if property_support.include?(prop) and
371
- property_support[prop].include?('support') and
372
- property_support[prop]['support'] >= @options[:warn_level]
373
- warnings.push({:message => "#{prop} CSS property",
374
- :level => WARN_LABEL[property_support[prop]['support']],
375
- :clients => property_support[prop]['unsupported_in'].join(', ')})
497
+ property_support = @client_support['css_properties']
498
+ properties.each do |prop|
499
+ if property_support.include?(prop) and
500
+ property_support[prop].include?('support') and
501
+ property_support[prop]['support'] >= @options[:warn_level]
502
+ warnings.push({:message => "#{prop} CSS property",
503
+ :level => WARN_LABEL[property_support[prop]['support']],
504
+ :clients => property_support[prop]['unsupported_in'].join(', ')})
505
+ end
376
506
  end
377
- end
378
507
 
379
- @client_support['attributes'].each do |attribute, data|
380
- next unless data['support'] >= @options[:warn_level]
381
- if @doc.search("*[@#{attribute}]").length > 0
382
- warnings.push({:message => "#{attribute} HTML attribute",
383
- :level => WARN_LABEL[property_support[prop]['support']],
384
- :clients => property_support[prop]['unsupported_in'].join(', ')})
508
+ @client_support['attributes'].each do |attribute, data|
509
+ next unless data['support'] >= @options[:warn_level]
510
+ if @doc.search("*[@#{attribute}]").length > 0
511
+ warnings.push({:message => "#{attribute} HTML attribute",
512
+ :level => WARN_LABEL[property_support[prop]['support']],
513
+ :clients => property_support[prop]['unsupported_in'].join(', ')})
514
+ end
385
515
  end
386
- end
387
516
 
388
- @client_support['elements'].each do |element, data|
389
- next unless data['support'] >= @options[:warn_level]
390
- if @doc.search("element").length > 0
391
- warnings.push({:message => "#{element} HTML element",
392
- :level => WARN_LABEL[property_support[prop]['support']],
393
- :clients => property_support[prop]['unsupported_in'].join(', ')})
517
+ @client_support['elements'].each do |element, data|
518
+ next unless data['support'] >= @options[:warn_level]
519
+ if @doc.search("element").length > 0
520
+ warnings.push({:message => "#{element} HTML element",
521
+ :level => WARN_LABEL[property_support[prop]['support']],
522
+ :clients => property_support[prop]['unsupported_in'].join(', ')})
523
+ end
394
524
  end
395
- end
396
525
 
397
- return warnings
526
+ return warnings
527
+ end
398
528
  end
399
- end
400
529
 
401
530
 
402
531
 
metadata CHANGED
@@ -1,7 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: premailer
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.5.4
4
+ hash: 15
5
+ prerelease: false
6
+ segments:
7
+ - 1
8
+ - 5
9
+ - 6
10
+ version: 1.5.6
5
11
  platform: ruby
6
12
  authors:
7
13
  - Alex Dunae
@@ -9,69 +15,74 @@ autorequire:
9
15
  bindir: bin
10
16
  cert_chain: []
11
17
 
12
- date: 2009-12-03 00:00:00 -08:00
18
+ date: 2010-11-03 00:00:00 -07:00
13
19
  default_executable:
14
20
  dependencies:
15
21
  - !ruby/object:Gem::Dependency
16
- name: hpricot
17
- type: :runtime
18
- version_requirement:
19
- version_requirements: !ruby/object:Gem::Requirement
22
+ name: nokogiri
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
20
26
  requirements:
21
27
  - - ">="
22
28
  - !ruby/object:Gem::Version
23
- version: "0.6"
24
- version:
29
+ hash: 7
30
+ segments:
31
+ - 1
32
+ - 4
33
+ - 0
34
+ version: 1.4.0
35
+ type: :runtime
36
+ version_requirements: *id001
25
37
  - !ruby/object:Gem::Dependency
26
38
  name: css_parser
27
- type: :runtime
28
- version_requirement:
29
- version_requirements: !ruby/object:Gem::Requirement
39
+ prerelease: false
40
+ requirement: &id002 !ruby/object:Gem::Requirement
41
+ none: false
30
42
  requirements:
31
43
  - - ">="
32
44
  - !ruby/object:Gem::Version
33
- version: 0.9.0
34
- version:
35
- - !ruby/object:Gem::Dependency
36
- name: text-reform
45
+ hash: 21
46
+ segments:
47
+ - 1
48
+ - 1
49
+ - 3
50
+ version: 1.1.3
37
51
  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:
52
+ version_requirements: *id002
45
53
  - !ruby/object:Gem::Dependency
46
54
  name: htmlentities
47
- type: :runtime
48
- version_requirement:
49
- version_requirements: !ruby/object:Gem::Requirement
55
+ prerelease: false
56
+ requirement: &id003 !ruby/object:Gem::Requirement
57
+ none: false
50
58
  requirements:
51
59
  - - ">="
52
60
  - !ruby/object:Gem::Version
61
+ hash: 63
62
+ segments:
63
+ - 4
64
+ - 0
65
+ - 0
53
66
  version: 4.0.0
54
- version:
67
+ type: :runtime
68
+ version_requirements: *id003
55
69
  description: Improve the rendering of HTML emails by making CSS inline, converting links and warning about unsupported code.
56
70
  email: code@dunae.ca
57
71
  executables:
58
72
  - premailer
59
73
  extensions: []
60
74
 
61
- extra_rdoc_files: []
62
-
75
+ extra_rdoc_files:
76
+ - README.rdoc
63
77
  files:
64
78
  - init.rb
65
- - rakefile.rb
79
+ - bin/premailer
80
+ - bin/trollop.rb
66
81
  - lib/premailer.rb
67
82
  - lib/premailer/html_to_plain_text.rb
68
83
  - lib/premailer/premailer.rb
69
- - CHANGELOG.rdoc
70
- - LICENSE.rdoc
71
- - README.rdoc
72
84
  - misc/client_support.yaml
73
- - bin/premailer
74
- - bin/trollop.rb
85
+ - README.rdoc
75
86
  has_rdoc: true
76
87
  homepage: http://premailer.dialect.ca/
77
88
  licenses: []
@@ -86,21 +97,27 @@ rdoc_options:
86
97
  require_paths:
87
98
  - lib
88
99
  required_ruby_version: !ruby/object:Gem::Requirement
100
+ none: false
89
101
  requirements:
90
102
  - - ">="
91
103
  - !ruby/object:Gem::Version
104
+ hash: 3
105
+ segments:
106
+ - 0
92
107
  version: "0"
93
- version:
94
108
  required_rubygems_version: !ruby/object:Gem::Requirement
109
+ none: false
95
110
  requirements:
96
111
  - - ">="
97
112
  - !ruby/object:Gem::Version
113
+ hash: 3
114
+ segments:
115
+ - 0
98
116
  version: "0"
99
- version:
100
117
  requirements: []
101
118
 
102
119
  rubyforge_project:
103
- rubygems_version: 1.3.5
120
+ rubygems_version: 1.3.7
104
121
  signing_key:
105
122
  specification_version: 3
106
123
  summary: Preflight for HTML e-mail.
@@ -1,57 +0,0 @@
1
- = Premailer CHANGELOG
2
-
3
- == Version 1.5.4
4
- * new bin/premailer script
5
- * added missing htmlentities depenency to gemspec (thanks to http://github.com/usefulthink )
6
- * fixed handling of unspecified <link> media types
7
-
8
- == Version 1.5.3
9
- * improved plaintext conversion
10
-
11
- == Version 1.5.2
12
- * released to GitHub
13
- * fixed handling of mailto links
14
- * various minor updates
15
-
16
- == Version 1.5.1
17
- * 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
18
- * bugfix (http://code.google.com/p/premailer/issues/detail?id=4) thanks to Dave Holmes
19
-
20
- == Version 1.5.0
21
- * preview release of Ruby gem
22
-
23
- == Version 1.4
24
- * incremental parsing improvements
25
- * respect <tt>@media</tt> rule (http://www.w3.org/TR/CSS21/media.html#at-media-rule)
26
- * better quote escaping
27
-
28
- == Version 1.3
29
- * separate CSS parser into its own library
30
- * handle <tt>background: red url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAADElEQVR42mP4%2F58BAAT%2FAf9jgNErAAAAAElFTkSuQmCC);</tt>
31
- * preserve <tt>:hover</tt> etc... in head styles
32
-
33
- == Version 1.2
34
- * respect <tt>LINK</tt> media types
35
- * better style folding
36
- * incremental parsing improvements
37
-
38
- == Version 1.1
39
- * proper calculation of selector specificity per CSS 2.1 spec
40
- * support for <tt>@import</tt>
41
- * preliminary support for shorthand CSS properties (<tt>margin</tt>, <tt>padding</tt>)
42
- * preliminary separation of CSS parser
43
-
44
- == Version 1.0
45
- * ported web interface to eRuby
46
- * incremental parsing improvements
47
-
48
- == Version 0.9
49
- * initial proof-of-concept
50
- * PHP web version
51
-
52
- == TODO: Future
53
- * complete shorthand properties support (<tt>border-width</tt>, <tt>font</tt>, <tt>background</tt>)
54
- * UTF-8 and other charsets (test page: http://kianga.kcore.de/2004/09/21/utf8_test)
55
- * make warnings for <tt>border</tt> match <tt>border-left</tt>, etc...
56
- * Integrate CSS validator
57
- * Remove unused classes and IDs
@@ -1,42 +0,0 @@
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.
@@ -1,42 +0,0 @@
1
- require 'rake'
2
- require 'fileutils'
3
- require 'lib/premailer'
4
-
5
- desc 'Default: parse a URL.'
6
- task :default => [:inline]
7
-
8
- desc 'Parse a URL and write out the output.'
9
- task :inline do
10
- url = ENV['url']
11
- output = ENV['output']
12
-
13
- if !url or url.empty? or !output or output.empty?
14
- puts 'Usage: rake inline url=http://example.com/ output=output.html'
15
- exit
16
- end
17
-
18
- premailer = Premailer.new(url, :warn_level => Premailer::Warnings::SAFE)
19
- fout = File.open(output, "w")
20
- fout.puts premailer.to_inline_css
21
- fout.close
22
-
23
- puts "Succesfully parsed '#{url}' into '#{output}'"
24
- puts premailer.warnings.length.to_s + ' CSS warnings were found'
25
- end
26
-
27
- task :text do
28
- url = ENV['url']
29
- output = ENV['output']
30
-
31
- if !url or url.empty? or !output or output.empty?
32
- puts 'Usage: rake text url=http://example.com/ output=output.txt'
33
- exit
34
- end
35
-
36
- premailer = Premailer.new(url, :warn_level => Premailer::Warnings::SAFE)
37
- fout = File.open(output, "w")
38
- fout.puts premailer.to_plain_text
39
- fout.close
40
-
41
- puts "Succesfully parsed '#{url}' into '#{output}'"
42
- end