premailer 1.7.0 → 1.7.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: