premailer 1.7.0 → 1.7.1

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.
@@ -48,12 +48,8 @@ Download the Premailer gem from RubyGems.
48
48
  Contributions are most welcome. Premailer was rotting away in a private SVN repository for too long and could use some TLC. Fork and patch to your heart's content. Please don't increment the version numbers, though.
49
49
 
50
50
  A few areas that are particularly in need of love:
51
- * Testing suite
52
- There were unit tests but they were so funky that it was better to just strip them out.
53
- * Create a binary file for easing command line use, allowing the output to be piped in *nix systems
54
- * Test with Rails
51
+ * Improved test coverage
55
52
  * 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
53
 
58
54
  === Credits and code
59
55
 
@@ -4,8 +4,5 @@ require 'digest/md5'
4
4
  require 'cgi'
5
5
  require 'css_parser'
6
6
  require File.expand_path(File.dirname(__FILE__) + '/premailer/adapter')
7
- require File.expand_path(File.dirname(__FILE__) + '/premailer/adapter/hpricot')
8
- require File.expand_path(File.dirname(__FILE__) + '/premailer/adapter/nokogiri')
9
-
10
7
  require File.expand_path(File.dirname(__FILE__) + '/premailer/html_to_plain_text')
11
8
  require File.expand_path(File.dirname(__FILE__) + '/premailer/premailer')
@@ -4,46 +4,56 @@
4
4
  #
5
5
  # * nokogiri
6
6
  # * hpricot
7
- module Adapter
8
- DEFAULT = :hpricot
7
+ class Premailer
8
+ module Adapter
9
9
 
10
- # Returns the adapter to use. Defaults to <tt>Adapter::</tt>.
10
+ autoload :Hpricot, 'premailer/adapter/hpricot'
11
+ autoload :Nokogiri, 'premailer/adapter/nokogiri'
12
+
13
+ REQUIREMENT_MAP = [
14
+ ["hpricot", :hpricot],
15
+ ["nokogiri", :nokogiri],
16
+ ]
17
+
18
+ # Returns the adapter to use.
11
19
  def self.use
12
- @use ||= DEFAULT
20
+ return @use if @use
21
+ self.use = self.default
22
+ @use
13
23
  end
14
24
 
15
- # Sets the +adapter+ to use. Raises an +ArgumentError+ unless the +adapter+ exists.
16
- def self.use=(adapter)
17
- validate_adapter! adapter
18
- @use = adapter
25
+ # The default adapter based on what you currently have loaded and
26
+ # installed. First checks to see if any adapters are already loaded,
27
+ # then ckecks to see which are installed if none are loaded.
28
+ def self.default
29
+ return :hpricot if defined?(::Hpricot)
30
+ return :nokogiri if defined?(::Nokogiri)
31
+
32
+ REQUIREMENT_MAP.each do |(library, adapter)|
33
+ begin
34
+ require library
35
+ return adapter
36
+ rescue LoadError
37
+ next
38
+ end
39
+ end
40
+
41
+ raise "No suitable adapter for Premailer was found, please install hpricot or nokogiri"
19
42
  end
20
43
 
21
- # Returns a memoized +Hash+ of adapters.
22
- def self.adapters
23
- @adapters ||= {
24
- :nokogiri => { :class => Nokogiri, :require => "nokogiri" },
25
- :hpricot => { :class => Hpricot, :require => "hpricot" },
26
- }
44
+ # Sets the +adapter+ to use. Raises an +ArgumentError+ unless the +adapter+ exists.
45
+ def self.use=(new_adapter)
46
+ @use = find(new_adapter)
27
47
  end
28
48
 
29
49
  # Returns an +adapter+. Raises an +ArgumentError+ unless the +adapter+ exists.
30
50
  def self.find(adapter)
31
- validate_adapter! adapter
32
- load_adapter adapter
33
- end
51
+ return adapter if adapter.is_a?(Module)
34
52
 
35
- private
36
-
37
- # Raises an +ArgumentError+ unless the +adapter+ exists.
38
- def self.validate_adapter!(adapter)
39
- raise ArgumentError, "Invalid adapter: #{adapter}" unless adapters[adapter]
53
+ Premailer::Adapter.const_get("#{adapter.to_s.split('_').map{|s| s.capitalize}.join('')}")
54
+ rescue NameError
55
+ raise ArgumentError, "Invalid adapter: #{adapter}"
40
56
  end
41
57
 
42
- # Tries to load and return the given +adapter+ name and class and falls back to the +FALLBACK+ adapter.
43
- def self.load_adapter(adapter)
44
- require adapters[adapter][:require]
45
- [adapter, adapters[adapter][:class]]
46
- rescue LoadError
47
- puts "tried to use the #{adapter} adapter, but was unable to find the library in the LOAD_PATH."
48
- end
58
+ end
49
59
  end
@@ -1,180 +1,183 @@
1
+ require 'hpricot'
2
+
3
+ class Premailer
4
+ module Adapter
5
+ module Hpricot
6
+
7
+ # Merge CSS into the HTML document.
8
+ #
9
+ # Returns a string.
10
+ def to_inline_css
11
+ doc = @processed_doc
12
+ @unmergable_rules = CssParser::Parser.new
13
+
14
+ # Give all styles already in style attributes a specificity of 1000
15
+ # per http://www.w3.org/TR/CSS21/cascade.html#specificity
16
+ doc.search("*[@style]").each do |el|
17
+ el['style'] = '[SPEC=1000[' + el.attributes['style'] + ']]'
18
+ end
1
19
 
2
- module Adapter
3
- module Hpricot
4
-
5
- # Merge CSS into the HTML document.
6
- #
7
- # Returns a string.
8
- def to_inline_css
9
- doc = @processed_doc
10
- @unmergable_rules = CssParser::Parser.new
11
-
12
- # Give all styles already in style attributes a specificity of 1000
13
- # per http://www.w3.org/TR/CSS21/cascade.html#specificity
14
- doc.search("*[@style]").each do |el|
15
- el['style'] = '[SPEC=1000[' + el.attributes['style'] + ']]'
16
- end
17
-
18
- # Iterate through the rules and merge them into the HTML
19
- @css_parser.each_selector(:all) do |selector, declaration, specificity|
20
- # Save un-mergable rules separately
21
- selector.gsub!(/:link([\s]*)+/i) {|m| $1 }
22
-
23
- # Convert element names to lower case
24
- selector.gsub!(/([\s]|^)([\w]+)/) {|m| $1.to_s + $2.to_s.downcase }
25
-
26
- if selector =~ Premailer::RE_UNMERGABLE_SELECTORS
27
- @unmergable_rules.add_rule_set!(CssParser::RuleSet.new(selector, declaration)) unless @options[:preserve_styles]
28
- else
29
- begin
30
- # Change single ID CSS selectors into xpath so that we can match more
31
- # than one element. Added to work around dodgy generated code.
32
- selector.gsub!(/\A\#([\w_\-]+)\Z/, '*[@id=\1]')
33
-
34
- doc.search(selector).each do |el|
35
- if el.elem? and (el.name != 'head' and el.parent.name != 'head')
36
- # Add a style attribute or append to the existing one
37
- block = "[SPEC=#{specificity}[#{declaration}]]"
38
- el['style'] = (el.attributes['style'].to_s ||= '') + ' ' + block
20
+ # Iterate through the rules and merge them into the HTML
21
+ @css_parser.each_selector(:all) do |selector, declaration, specificity|
22
+ # Save un-mergable rules separately
23
+ selector.gsub!(/:link([\s]*)+/i) {|m| $1 }
24
+
25
+ # Convert element names to lower case
26
+ selector.gsub!(/([\s]|^)([\w]+)/) {|m| $1.to_s + $2.to_s.downcase }
27
+
28
+ if selector =~ Premailer::RE_UNMERGABLE_SELECTORS
29
+ @unmergable_rules.add_rule_set!(CssParser::RuleSet.new(selector, declaration)) unless @options[:preserve_styles]
30
+ else
31
+ begin
32
+ # Change single ID CSS selectors into xpath so that we can match more
33
+ # than one element. Added to work around dodgy generated code.
34
+ selector.gsub!(/\A\#([\w_\-]+)\Z/, '*[@id=\1]')
35
+
36
+ doc.search(selector).each do |el|
37
+ if el.elem? and (el.name != 'head' and el.parent.name != 'head')
38
+ # Add a style attribute or append to the existing one
39
+ block = "[SPEC=#{specificity}[#{declaration}]]"
40
+ el['style'] = (el.attributes['style'].to_s ||= '') + ' ' + block
41
+ end
42
+ end
43
+ rescue ::Hpricot::Error, RuntimeError, ArgumentError
44
+ $stderr.puts "CSS syntax error with selector: #{selector}" if @options[:verbose]
45
+ next
39
46
  end
40
47
  end
41
- rescue Hpricot::Error, RuntimeError, ArgumentError
42
- $stderr.puts "CSS syntax error with selector: #{selector}" if @options[:verbose]
43
- next
44
48
  end
45
- end
46
- end
47
49
 
48
- # Read STYLE attributes and perform folding
49
- doc.search("*[@style]").each do |el|
50
- style = el.attributes['style'].to_s
51
-
52
- declarations = []
50
+ # Read STYLE attributes and perform folding
51
+ doc.search("*[@style]").each do |el|
52
+ style = el.attributes['style'].to_s
53
53
 
54
- style.scan(/\[SPEC\=([\d]+)\[(.[^\]\]]*)\]\]/).each do |declaration|
55
- rs = CssParser::RuleSet.new(nil, declaration[1].to_s, declaration[0].to_i)
56
- declarations << rs
57
- end
54
+ declarations = []
55
+
56
+ style.scan(/\[SPEC\=([\d]+)\[(.[^\]\]]*)\]\]/).each do |declaration|
57
+ rs = CssParser::RuleSet.new(nil, declaration[1].to_s, declaration[0].to_i)
58
+ declarations << rs
59
+ end
58
60
 
59
- # Perform style folding
60
- merged = CssParser.merge(declarations)
61
- merged.expand_shorthand!
62
-
63
- # Duplicate CSS attributes as HTML attributes
64
- if Premailer::RELATED_ATTRIBUTES.has_key?(el.name)
65
- Premailer::RELATED_ATTRIBUTES[el.name].each do |css_att, html_att|
66
- el[html_att] = merged[css_att].gsub(/;$/, '').strip if el[html_att].nil? and not merged[css_att].empty?
61
+ # Perform style folding
62
+ merged = CssParser.merge(declarations)
63
+ merged.expand_shorthand!
64
+
65
+ # Duplicate CSS attributes as HTML attributes
66
+ if Premailer::RELATED_ATTRIBUTES.has_key?(el.name)
67
+ Premailer::RELATED_ATTRIBUTES[el.name].each do |css_att, html_att|
68
+ el[html_att] = merged[css_att].gsub(/;$/, '').strip if el[html_att].nil? and not merged[css_att].empty?
69
+ end
70
+ end
71
+
72
+ merged.create_dimensions_shorthand!
73
+
74
+ # write the inline STYLE attribute
75
+ el['style'] = Premailer.escape_string(merged.declarations_to_s)
67
76
  end
68
- end
69
-
70
- merged.create_dimensions_shorthand!
71
77
 
72
- # write the inline STYLE attribute
73
- el['style'] = Premailer.escape_string(merged.declarations_to_s)
74
- end
78
+ doc = write_unmergable_css_rules(doc, @unmergable_rules)
75
79
 
76
- doc = write_unmergable_css_rules(doc, @unmergable_rules)
80
+ if @options[:remove_classes] or @options[:remove_comments]
81
+ doc.search('*').each do |el|
82
+ if el.comment? and @options[:remove_comments]
83
+ lst = el.parent.children
84
+ el.parent = nil
85
+ lst.delete(el)
86
+ elsif el.elem?
87
+ el.remove_attribute('class') if @options[:remove_classes]
88
+ end
89
+ end
90
+ end
77
91
 
78
- if @options[:remove_classes] or @options[:remove_comments]
79
- doc.search('*').each do |el|
80
- if el.comment? and @options[:remove_comments]
81
- lst = el.parent.children
82
- el.parent = nil
83
- lst.delete(el)
84
- elsif el.elem?
85
- el.remove_attribute('class') if @options[:remove_classes]
92
+ if @options[:remove_ids]
93
+ # find all anchor's targets and hash them
94
+ targets = []
95
+ doc.search("a[@href^='#']").each do |el|
96
+ target = el.get_attribute('href')[1..-1]
97
+ targets << target
98
+ el.set_attribute('href', "#" + Digest::MD5.hexdigest(target))
99
+ end
100
+ # hash ids that are links target, delete others
101
+ doc.search("*[@id]").each do |el|
102
+ id = el.get_attribute('id')
103
+ if targets.include?(id)
104
+ el.set_attribute('id', Digest::MD5.hexdigest(id))
105
+ else
106
+ el.remove_attribute('id')
107
+ end
108
+ end
86
109
  end
87
- end
88
- end
89
110
 
90
- if @options[:remove_ids]
91
- # find all anchor's targets and hash them
92
- targets = []
93
- doc.search("a[@href^='#']").each do |el|
94
- target = el.get_attribute('href')[1..-1]
95
- targets << target
96
- el.set_attribute('href', "#" + Digest::MD5.hexdigest(target))
111
+ @processed_doc = doc
112
+
113
+ @processed_doc.to_original_html
97
114
  end
98
- # hash ids that are links target, delete others
99
- doc.search("*[@id]").each do |el|
100
- id = el.get_attribute('id')
101
- if targets.include?(id)
102
- el.set_attribute('id', Digest::MD5.hexdigest(id))
115
+
116
+ # Create a <tt>style</tt> element with un-mergable rules (e.g. <tt>:hover</tt>)
117
+ # and write it into the <tt>body</tt>.
118
+ #
119
+ # <tt>doc</tt> is an Hpricot document and <tt>unmergable_css_rules</tt> is a Css::RuleSet.
120
+ #
121
+ # Returns an Hpricot document.
122
+ def write_unmergable_css_rules(doc, unmergable_rules) # :nodoc:
123
+ if head = doc.search('head')
124
+ styles = ''
125
+ unmergable_rules.each_selector(:all, :force_important => true) do |selector, declarations, specificity|
126
+ styles += "#{selector} { #{declarations} }\n"
127
+ end
128
+
129
+ unless styles.empty?
130
+ style_tag = "\n<style type=\"text/css\">\n#{styles}</style>\n"
131
+ head.html.empty? ? head.inner_html(style_tag) : head.append(style_tag)
132
+ end
103
133
  else
104
- el.remove_attribute('id')
134
+ $stderr.puts "Unable to write unmergable CSS rules: no <head> was found" if @options[:verbose]
105
135
  end
136
+ doc
106
137
  end
107
- end
108
138
 
109
- @processed_doc = doc
110
139
 
111
- @processed_doc.to_original_html
112
- end
113
-
114
- # Create a <tt>style</tt> element with un-mergable rules (e.g. <tt>:hover</tt>)
115
- # and write it into the <tt>body</tt>.
116
- #
117
- # <tt>doc</tt> is an Hpricot document and <tt>unmergable_css_rules</tt> is a Css::RuleSet.
118
- #
119
- # Returns an Hpricot document.
120
- def write_unmergable_css_rules(doc, unmergable_rules) # :nodoc:
121
- if head = doc.search('head')
122
- styles = ''
123
- unmergable_rules.each_selector(:all, :force_important => true) do |selector, declarations, specificity|
124
- styles += "#{selector} { #{declarations} }\n"
125
- end
126
-
127
- unless styles.empty?
128
- style_tag = "\n<style type=\"text/css\">\n#{styles}</style>\n"
129
- head.html.empty? ? head.inner_html(style_tag) : head.append(style_tag)
140
+ # Converts the HTML document to a format suitable for plain-text e-mail.
141
+ #
142
+ # If present, uses the <body> element as its base; otherwise uses the whole document.
143
+ #
144
+ # Returns a string.
145
+ def to_plain_text
146
+ html_src = ''
147
+ begin
148
+ html_src = @doc.search("body").inner_html
149
+ rescue; end
150
+
151
+ html_src = @doc.to_html unless html_src and not html_src.empty?
152
+ convert_to_text(html_src, @options[:line_length], @html_encoding)
130
153
  end
131
- else
132
- $stderr.puts "Unable to write unmergable CSS rules: no <head> was found" if @options[:verbose]
133
- end
134
- doc
135
- end
136
154
 
137
-
138
- # Converts the HTML document to a format suitable for plain-text e-mail.
139
- #
140
- # If present, uses the <body> element as its base; otherwise uses the whole document.
141
- #
142
- # Returns a string.
143
- def to_plain_text
144
- html_src = ''
145
- begin
146
- html_src = @doc.search("body").inner_html
147
- rescue; end
148
-
149
- html_src = @doc.to_html unless html_src and not html_src.empty?
150
- convert_to_text(html_src, @options[:line_length], @html_encoding)
151
- end
152
-
153
-
154
- # Returns the original HTML as a string.
155
- def to_s
156
- @doc.to_original_html
157
- end
158
-
159
- # Load the HTML file and convert it into an Hpricot document.
160
- #
161
- # Returns an Hpricot document.
162
- def load_html(input) # :nodoc:
163
- thing = nil
164
-
165
- # TODO: duplicate options
166
- if @options[:with_html_string] or @options[:inline] or input.respond_to?(:read)
167
- thing = input
155
+
156
+ # Returns the original HTML as a string.
157
+ def to_s
158
+ @doc.to_original_html
159
+ end
160
+
161
+ # Load the HTML file and convert it into an Hpricot document.
162
+ #
163
+ # Returns an Hpricot document.
164
+ def load_html(input) # :nodoc:
165
+ thing = nil
166
+
167
+ # TODO: duplicate options
168
+ if @options[:with_html_string] or @options[:inline] or input.respond_to?(:read)
169
+ thing = input
168
170
  elsif @is_local_file
169
- @base_dir = File.dirname(input)
170
- thing = File.open(input, 'r')
171
+ @base_dir = File.dirname(input)
172
+ thing = File.open(input, 'r')
171
173
  else
172
- thing = open(input)
173
- end
174
-
175
- # TODO: deal with Hpricot seg faults on empty input
176
- thing ? Hpricot(thing) : nil
177
- end
178
-
179
- end
174
+ thing = open(input)
175
+ end
176
+
177
+ # TODO: deal with Hpricot seg faults on empty input
178
+ thing ? Hpricot(thing) : nil
179
+ end
180
+
181
+ end
182
+ end
180
183
  end
@@ -1,199 +1,203 @@
1
- module Adapter
2
- module Nokogiri
3
-
4
- # Merge CSS into the HTML document.
5
- #
6
- # Returns a string.
7
- def to_inline_css
8
- doc = @processed_doc
9
- @unmergable_rules = CssParser::Parser.new
10
-
11
- # Give all styles already in style attributes a specificity of 1000
12
- # per http://www.w3.org/TR/CSS21/cascade.html#specificity
13
- doc.search("*[@style]").each do |el|
14
- el['style'] = '[SPEC=1000[' + el.attributes['style'] + ']]'
15
- end
1
+ require 'nokogiri'
2
+
3
+ class Premailer
4
+ module Adapter
5
+ module Nokogiri
6
+
7
+ # Merge CSS into the HTML document.
8
+ #
9
+ # Returns a string.
10
+ def to_inline_css
11
+ doc = @processed_doc
12
+ @unmergable_rules = CssParser::Parser.new
13
+
14
+ # Give all styles already in style attributes a specificity of 1000
15
+ # per http://www.w3.org/TR/CSS21/cascade.html#specificity
16
+ doc.search("*[@style]").each do |el|
17
+ el['style'] = '[SPEC=1000[' + el.attributes['style'] + ']]'
18
+ end
16
19
 
17
- # Iterate through the rules and merge them into the HTML
18
- @css_parser.each_selector(:all) do |selector, declaration, specificity|
19
- # Save un-mergable rules separately
20
- selector.gsub!(/:link([\s]*)+/i) {|m| $1 }
20
+ # Iterate through the rules and merge them into the HTML
21
+ @css_parser.each_selector(:all) do |selector, declaration, specificity|
22
+ # Save un-mergable rules separately
23
+ selector.gsub!(/:link([\s]*)+/i) {|m| $1 }
21
24
 
22
- # Convert element names to lower case
23
- selector.gsub!(/([\s]|^)([\w]+)/) {|m| $1.to_s + $2.to_s.downcase }
24
-
25
- if selector =~ Premailer::RE_UNMERGABLE_SELECTORS
26
- @unmergable_rules.add_rule_set!(CssParser::RuleSet.new(selector, declaration)) unless @options[:preserve_styles]
27
- else
28
- begin
29
- # Change single ID CSS selectors into xpath so that we can match more
30
- # than one element. Added to work around dodgy generated code.
31
- selector.gsub!(/\A\#([\w_\-]+)\Z/, '*[@id=\1]')
32
-
33
- doc.search(selector).each do |el|
34
- if el.elem? and (el.name != 'head' and el.parent.name != 'head')
35
- # Add a style attribute or append to the existing one
36
- block = "[SPEC=#{specificity}[#{declaration}]]"
37
- el['style'] = (el.attributes['style'].to_s ||= '') + ' ' + block
25
+ # Convert element names to lower case
26
+ selector.gsub!(/([\s]|^)([\w]+)/) {|m| $1.to_s + $2.to_s.downcase }
27
+
28
+ if selector =~ Premailer::RE_UNMERGABLE_SELECTORS
29
+ @unmergable_rules.add_rule_set!(CssParser::RuleSet.new(selector, declaration)) unless @options[:preserve_styles]
30
+ else
31
+ begin
32
+ # Change single ID CSS selectors into xpath so that we can match more
33
+ # than one element. Added to work around dodgy generated code.
34
+ selector.gsub!(/\A\#([\w_\-]+)\Z/, '*[@id=\1]')
35
+
36
+ doc.search(selector).each do |el|
37
+ if el.elem? and (el.name != 'head' and el.parent.name != 'head')
38
+ # Add a style attribute or append to the existing one
39
+ block = "[SPEC=#{specificity}[#{declaration}]]"
40
+ el['style'] = (el.attributes['style'].to_s ||= '') + ' ' + block
41
+ end
38
42
  end
43
+ rescue ::Nokogiri::SyntaxError, RuntimeError, ArgumentError
44
+ $stderr.puts "CSS syntax error with selector: #{selector}" if @options[:verbose]
45
+ next
39
46
  end
40
- rescue ::Nokogiri::SyntaxError, RuntimeError, ArgumentError
41
- $stderr.puts "CSS syntax error with selector: #{selector}" if @options[:verbose]
42
- next
43
47
  end
44
48
  end
45
- end
46
49
 
47
- # Read STYLE attributes and perform folding
48
- doc.search("*[@style]").each do |el|
49
- style = el.attributes['style'].to_s
50
-
51
- declarations = []
50
+ # Read STYLE attributes and perform folding
51
+ doc.search("*[@style]").each do |el|
52
+ style = el.attributes['style'].to_s
52
53
 
53
- style.scan(/\[SPEC\=([\d]+)\[(.[^\]\]]*)\]\]/).each do |declaration|
54
- rs = CssParser::RuleSet.new(nil, declaration[1].to_s, declaration[0].to_i)
55
- declarations << rs
56
- end
54
+ declarations = []
57
55
 
58
- # Perform style folding
59
- merged = CssParser.merge(declarations)
60
- merged.expand_shorthand!
61
-
62
- # Duplicate CSS attributes as HTML attributes
63
- if Premailer::RELATED_ATTRIBUTES.has_key?(el.name)
64
- Premailer::RELATED_ATTRIBUTES[el.name].each do |css_att, html_att|
65
- el[html_att] = merged[css_att].gsub(/;$/, '').strip if el[html_att].nil? and not merged[css_att].empty?
56
+ style.scan(/\[SPEC\=([\d]+)\[(.[^\]\]]*)\]\]/).each do |declaration|
57
+ rs = CssParser::RuleSet.new(nil, declaration[1].to_s, declaration[0].to_i)
58
+ declarations << rs
66
59
  end
60
+
61
+ # Perform style folding
62
+ merged = CssParser.merge(declarations)
63
+ merged.expand_shorthand!
64
+
65
+ # Duplicate CSS attributes as HTML attributes
66
+ if Premailer::RELATED_ATTRIBUTES.has_key?(el.name)
67
+ Premailer::RELATED_ATTRIBUTES[el.name].each do |css_att, html_att|
68
+ el[html_att] = merged[css_att].gsub(/;$/, '').strip if el[html_att].nil? and not merged[css_att].empty?
69
+ end
70
+ end
71
+
72
+ merged.create_dimensions_shorthand!
73
+
74
+ # write the inline STYLE attribute
75
+ el['style'] = Premailer.escape_string(merged.declarations_to_s)
67
76
  end
68
-
69
- merged.create_dimensions_shorthand!
70
77
 
71
- # write the inline STYLE attribute
72
- el['style'] = Premailer.escape_string(merged.declarations_to_s)
73
- end
78
+ doc = write_unmergable_css_rules(doc, @unmergable_rules)
74
79
 
75
- doc = write_unmergable_css_rules(doc, @unmergable_rules)
80
+ if @options[:remove_classes] or @options[:remove_comments]
81
+ doc.traverse do |el|
82
+ if el.comment? and @options[:remove_comments]
83
+ el.remove
84
+ elsif el.element?
85
+ el.remove_attribute('class') if @options[:remove_classes]
86
+ end
87
+ end
88
+ end
76
89
 
77
- if @options[:remove_classes] or @options[:remove_comments]
78
- doc.traverse do |el|
79
- if el.comment? and @options[:remove_comments]
80
- el.remove
81
- elsif el.element?
82
- el.remove_attribute('class') if @options[:remove_classes]
90
+ if @options[:remove_ids]
91
+ # find all anchor's targets and hash them
92
+ targets = []
93
+ doc.search("a[@href^='#']").each do |el|
94
+ target = el.get_attribute('href')[1..-1]
95
+ targets << target
96
+ el.set_attribute('href', "#" + Digest::MD5.hexdigest(target))
83
97
  end
84
- end
85
- end
98
+ # hash ids that are links target, delete others
99
+ doc.search("*[@id]").each do |el|
100
+ id = el.get_attribute('id')
101
+ if targets.include?(id)
102
+ el.set_attribute('id', Digest::MD5.hexdigest(id))
103
+ else
104
+ el.remove_attribute('id')
105
+ end
106
+ end
107
+ end
86
108
 
87
- if @options[:remove_ids]
88
- # find all anchor's targets and hash them
89
- targets = []
90
- doc.search("a[@href^='#']").each do |el|
91
- target = el.get_attribute('href')[1..-1]
92
- targets << target
93
- el.set_attribute('href', "#" + Digest::MD5.hexdigest(target))
109
+ @processed_doc = doc
110
+ if is_xhtml?
111
+ @processed_doc.to_xhtml
112
+ else
113
+ @processed_doc.to_html
94
114
  end
95
- # hash ids that are links target, delete others
96
- doc.search("*[@id]").each do |el|
97
- id = el.get_attribute('id')
98
- if targets.include?(id)
99
- el.set_attribute('id', Digest::MD5.hexdigest(id))
100
- else
101
- el.remove_attribute('id')
115
+ end
116
+
117
+ # Create a <tt>style</tt> element with un-mergable rules (e.g. <tt>:hover</tt>)
118
+ # and write it into the <tt>body</tt>.
119
+ #
120
+ # <tt>doc</tt> is an Nokogiri document and <tt>unmergable_css_rules</tt> is a Css::RuleSet.
121
+ #
122
+ # Returns an Nokogiri document.
123
+ def write_unmergable_css_rules(doc, unmergable_rules) # :nodoc:
124
+ if head = doc.at('head')
125
+ styles = ''
126
+ unmergable_rules.each_selector(:all, :force_important => true) do |selector, declarations, specificity|
127
+ styles += "#{selector} { #{declarations} }\n"
102
128
  end
129
+
130
+ unless styles.empty?
131
+ style_tag = "\n<style type=\"text/css\">\n#{styles}</style>\n"
132
+
133
+ head.add_child(style_tag)
134
+ end
135
+ else
136
+ $stderr.puts "Unable to write unmergable CSS rules: no <head> was found" if @options[:verbose]
103
137
  end
138
+ doc
104
139
  end
105
140
 
106
- @processed_doc = doc
107
- if is_xhtml?
108
- @processed_doc.to_xhtml
109
- else
110
- @processed_doc.to_html
111
- end
112
- end
113
-
114
- # Create a <tt>style</tt> element with un-mergable rules (e.g. <tt>:hover</tt>)
115
- # and write it into the <tt>body</tt>.
116
- #
117
- # <tt>doc</tt> is an Nokogiri document and <tt>unmergable_css_rules</tt> is a Css::RuleSet.
118
- #
119
- # Returns an Nokogiri document.
120
- def write_unmergable_css_rules(doc, unmergable_rules) # :nodoc:
121
- if head = doc.at('head')
122
- styles = ''
123
- unmergable_rules.each_selector(:all, :force_important => true) do |selector, declarations, specificity|
124
- styles += "#{selector} { #{declarations} }\n"
125
- end
126
-
127
- unless styles.empty?
128
- style_tag = "\n<style type=\"text/css\">\n#{styles}</style>\n"
129
-
130
- head.add_child(style_tag)
141
+
142
+ # Converts the HTML document to a format suitable for plain-text e-mail.
143
+ #
144
+ # If present, uses the <body> element as its base; otherwise uses the whole document.
145
+ #
146
+ # Returns a string.
147
+ def to_plain_text
148
+ html_src = ''
149
+ begin
150
+ html_src = @doc.at("body").inner_html
151
+ rescue; end
152
+
153
+ html_src = @doc.to_html unless html_src and not html_src.empty?
154
+ convert_to_text(html_src, @options[:line_length], @html_encoding)
155
+ end
156
+
157
+ # Returns the original HTML as a string.
158
+ def to_s
159
+ if is_xhtml?
160
+ @doc.to_xhtml
161
+ else
162
+ @doc.to_html
131
163
  end
132
- else
133
- $stderr.puts "Unable to write unmergable CSS rules: no <head> was found" if @options[:verbose]
134
164
  end
135
- doc
136
- end
137
165
 
138
-
139
- # Converts the HTML document to a format suitable for plain-text e-mail.
140
- #
141
- # If present, uses the <body> element as its base; otherwise uses the whole document.
142
- #
143
- # Returns a string.
144
- def to_plain_text
145
- html_src = ''
146
- begin
147
- html_src = @doc.at("body").inner_html
148
- rescue; end
149
-
150
- html_src = @doc.to_html unless html_src and not html_src.empty?
151
- convert_to_text(html_src, @options[:line_length], @html_encoding)
152
- end
153
-
154
- # Returns the original HTML as a string.
155
- def to_s
156
- if is_xhtml?
157
- @doc.to_xhtml
158
- else
159
- @doc.to_html
160
- end
161
- end
162
-
163
- # Load the HTML file and convert it into an Nokogiri document.
164
- #
165
- # Returns an Nokogiri document.
166
- def load_html(input) # :nodoc:
167
- thing = nil
168
-
169
- # TODO: duplicate options
170
- if @options[:with_html_string] or @options[:inline] or input.respond_to?(:read)
171
- thing = input
166
+ # Load the HTML file and convert it into an Nokogiri document.
167
+ #
168
+ # Returns an Nokogiri document.
169
+ def load_html(input) # :nodoc:
170
+ thing = nil
171
+
172
+ # TODO: duplicate options
173
+ if @options[:with_html_string] or @options[:inline] or input.respond_to?(:read)
174
+ thing = input
172
175
  elsif @is_local_file
173
- @base_dir = File.dirname(input)
174
- thing = File.open(input, 'r')
176
+ @base_dir = File.dirname(input)
177
+ thing = File.open(input, 'r')
175
178
  else
176
- thing = open(input)
177
- end
178
-
179
- if thing.respond_to?(:read)
180
- thing = thing.read
181
- end
182
-
183
- return nil unless thing
184
-
185
- doc = nil
186
-
187
- # Default encoding is ASCII-8BIT (binary) per http://groups.google.com/group/nokogiri-talk/msg/0b81ef0dc180dc74
188
- if thing.is_a?(String) and RUBY_VERSION =~ /1.9/
189
- thing = thing.force_encoding('ASCII-8BIT').encode!
190
- doc = ::Nokogiri::HTML(thing) {|c| c.noent.recover }
191
- else
192
- doc = ::Nokogiri::HTML(thing, nil, 'ASCII-8BIT') {|c| c.noent.recover }
193
- end
194
-
195
- return doc
196
- end
197
-
198
- end
179
+ thing = open(input)
180
+ end
181
+
182
+ if thing.respond_to?(:read)
183
+ thing = thing.read
184
+ end
185
+
186
+ return nil unless thing
187
+
188
+ doc = nil
189
+
190
+ # Default encoding is ASCII-8BIT (binary) per http://groups.google.com/group/nokogiri-talk/msg/0b81ef0dc180dc74
191
+ if thing.is_a?(String) and RUBY_VERSION =~ /1.9/
192
+ thing = thing.force_encoding('ASCII-8BIT').encode!
193
+ doc = ::Nokogiri::HTML(thing) {|c| c.noent.recover }
194
+ else
195
+ doc = ::Nokogiri::HTML(thing, nil, 'ASCII-8BIT') {|c| c.noent.recover }
196
+ end
197
+
198
+ return doc
199
+ end
200
+
201
+ end
202
+ end
199
203
  end
@@ -33,7 +33,7 @@ class Premailer
33
33
  include HtmlToPlainText
34
34
  include CssParser
35
35
 
36
- VERSION = '1.7.0'
36
+ VERSION = '1.7.1'
37
37
 
38
38
  CLIENT_SUPPORT_FILE = File.dirname(__FILE__) + '/../../misc/client_support.yaml'
39
39
 
@@ -151,8 +151,7 @@ class Premailer
151
151
  :io_exceptions => @options[:io_exceptions]
152
152
  })
153
153
 
154
- @adapter_name = @options[:adapter]
155
- @adapter_name, @adapter_class = Adapter.find @adapter_name
154
+ @adapter_class = Adapter.find @options[:adapter]
156
155
 
157
156
  self.class.send(:include, @adapter_class)
158
157
 
metadata CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 1
7
7
  - 7
8
- - 0
9
- version: 1.7.0
8
+ - 1
9
+ version: 1.7.1
10
10
  platform: ruby
11
11
  authors:
12
12
  - Alex Dunae
@@ -14,11 +14,11 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2011-12-31 00:00:00 -08:00
17
+ date: 2011-04-01 00:00:00 -07:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
21
- name: hpricot
21
+ name: css_parser
22
22
  prerelease: false
23
23
  requirement: &id001 !ruby/object:Gem::Requirement
24
24
  none: false
@@ -26,14 +26,14 @@ dependencies:
26
26
  - - ">="
27
27
  - !ruby/object:Gem::Version
28
28
  segments:
29
- - 0
30
- - 8
31
- - 3
32
- version: 0.8.3
29
+ - 1
30
+ - 1
31
+ - 9
32
+ version: 1.1.9
33
33
  type: :runtime
34
34
  version_requirements: *id001
35
35
  - !ruby/object:Gem::Dependency
36
- name: css_parser
36
+ name: htmlentities
37
37
  prerelease: false
38
38
  requirement: &id002 !ruby/object:Gem::Requirement
39
39
  none: false
@@ -41,14 +41,14 @@ dependencies:
41
41
  - - ">="
42
42
  - !ruby/object:Gem::Version
43
43
  segments:
44
- - 1
45
- - 1
46
- - 6
47
- version: 1.1.6
44
+ - 4
45
+ - 0
46
+ - 0
47
+ version: 4.0.0
48
48
  type: :runtime
49
49
  version_requirements: *id002
50
50
  - !ruby/object:Gem::Dependency
51
- name: htmlentities
51
+ name: hpricot
52
52
  prerelease: false
53
53
  requirement: &id003 !ruby/object:Gem::Requirement
54
54
  none: false
@@ -56,12 +56,27 @@ dependencies:
56
56
  - - ">="
57
57
  - !ruby/object:Gem::Version
58
58
  segments:
59
- - 4
60
- - 0
61
59
  - 0
62
- version: 4.0.0
63
- type: :runtime
60
+ - 8
61
+ - 3
62
+ version: 0.8.3
63
+ type: :development
64
64
  version_requirements: *id003
65
+ - !ruby/object:Gem::Dependency
66
+ name: nokogiri
67
+ prerelease: false
68
+ requirement: &id004 !ruby/object:Gem::Requirement
69
+ none: false
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ segments:
74
+ - 1
75
+ - 4
76
+ - 4
77
+ version: 1.4.4
78
+ type: :development
79
+ version_requirements: *id004
65
80
  description: Improve the rendering of HTML emails by making CSS inline, converting links and warning about unsupported code.
66
81
  email: code@dunae.ca
67
82
  executables: