premailer 1.8.6 → 1.8.7
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.
- checksums.yaml +4 -4
- data/README.md +4 -4
- data/bin/premailer +0 -1
- data/lib/premailer/adapter.rb +6 -2
- data/lib/premailer/adapter/hpricot.rb +12 -2
- data/lib/premailer/adapter/nokogiri.rb +8 -6
- data/lib/premailer/adapter/nokogumbo.rb +243 -0
- data/lib/premailer/html_to_plain_text.rb +9 -15
- data/lib/premailer/premailer.rb +11 -10
- data/lib/premailer/version.rb +1 -1
- metadata +64 -69
- data/.gitignore +0 -12
- data/.jrubyrc +0 -1
- data/.travis.yml +0 -34
- data/.yardopts +0 -9
- data/Gemfile +0 -16
- data/gemfiles/.ruby187.gemfile +0 -21
- data/init.rb +0 -1
- data/local-premailer +0 -9
- data/premailer.gemspec +0 -30
- data/rakefile.rb +0 -62
- data/test/files/base.html +0 -140
- data/test/files/chars.html +0 -6
- data/test/files/contact_bg.png +0 -0
- data/test/files/dialect.png +0 -0
- data/test/files/dots_end.png +0 -0
- data/test/files/dots_h.gif +0 -0
- data/test/files/html4.html +0 -12
- data/test/files/html_with_uri.html +0 -9
- data/test/files/ignore.css +0 -3
- data/test/files/ignore.html +0 -15
- data/test/files/import.css +0 -13
- data/test/files/inc/2009-placeholder.png +0 -0
- data/test/files/iso-8859-2.html +0 -1
- data/test/files/iso-8859-5.html +0 -8
- data/test/files/no_css.html +0 -11
- data/test/files/noimport.css +0 -13
- data/test/files/styles.css +0 -106
- data/test/files/xhtml.html +0 -11
- data/test/future_tests.rb +0 -50
- data/test/helper.rb +0 -42
- data/test/test_adapter.rb +0 -29
- data/test/test_html_to_plain_text.rb +0 -184
- data/test/test_links.rb +0 -207
- data/test/test_misc.rb +0 -373
- data/test/test_premailer.rb +0 -347
- data/test/test_warnings.rb +0 -95
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ceccb9e67d075de22a76d50df38d99481a6811bf
|
4
|
+
data.tar.gz: 475cc5974d20a0b8b69bef467d10de3edff7d821
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2591ab12195ae73b9909b945131cdf06382917afde4e4270dc496b937eac87c98cea081c825209a0e4cfd40f8b2202f1953f980d8203cf90afcadec80b139c17
|
7
|
+
data.tar.gz: e34eda9604977da572ef0f712c710547e9e7d965370404a2b1a233a2dd04760a7461c70acf4b40008b72824fe2a38f4cb18bd36d49da794bd61bd41a09096721
|
data/README.md
CHANGED
@@ -54,7 +54,7 @@ end
|
|
54
54
|
|
55
55
|
## Ruby Compatibility
|
56
56
|
|
57
|
-
Premailer is tested on Ruby 1.8.7, Ruby 1.9.2, Ruby 1.9.3, and Ruby 2.x.0 . It also works on REE. JRuby support is close; contributors are welcome. Checkout the latest build status on the [Travis CI dashboard](
|
57
|
+
Premailer is tested on Ruby 1.8.7, Ruby 1.9.2, Ruby 1.9.3, and Ruby 2.x.0 . It also works on REE. JRuby support is close; contributors are welcome. Checkout the latest build status on the [Travis CI dashboard](https://travis-ci.org/#!/premailer/premailer).
|
58
58
|
|
59
59
|
## Premailer-specific CSS
|
60
60
|
|
@@ -66,7 +66,7 @@ Premailer looks for a few CSS attributes that make working with tables a bit eas
|
|
66
66
|
| -premailer-height | Available on `table`, `tr`, `th` and `td` elements |
|
67
67
|
| -premailer-cellpadding | Available on `table` elements |
|
68
68
|
| -premailer-cellspacing | Available on `table` elements |
|
69
|
-
| data-premailer="ignore" | Available on `style` elements. Premailer will ignore these elements entirely. |
|
69
|
+
| data-premailer="ignore" | Available on `link` and `style` elements. Premailer will ignore these elements entirely. |
|
70
70
|
|
71
71
|
Each of these CSS declarations will be copied to appropriate element's attribute.
|
72
72
|
|
@@ -93,10 +93,10 @@ A few areas that are particularly in need of love:
|
|
93
93
|
|
94
94
|
## Credits and code
|
95
95
|
|
96
|
-
Thanks to [all the wonderful contributors](https://github.com/
|
96
|
+
Thanks to [all the wonderful contributors](https://github.com/premailer/premailer/contributors) for their updates.
|
97
97
|
|
98
98
|
Thanks to [Greenhood + Company](http://www.greenhood.com/) for sponsoring some of the 1.5.6 updates,
|
99
|
-
and to [Campaign Monitor](
|
99
|
+
and to [Campaign Monitor](https://www.campaignmonitor.com/) for supporting the web interface.
|
100
100
|
|
101
101
|
The web interface can be found at [premailer.dialect.ca](http://premailer.dialect.ca).
|
102
102
|
|
data/bin/premailer
CHANGED
data/lib/premailer/adapter.rb
CHANGED
@@ -4,16 +4,19 @@ class Premailer
|
|
4
4
|
# Manages the adapter classes. Currently supports:
|
5
5
|
#
|
6
6
|
# * nokogiri
|
7
|
+
# * nokogumbo
|
7
8
|
# * hpricot
|
8
9
|
module Adapter
|
9
10
|
|
10
11
|
autoload :Hpricot, 'premailer/adapter/hpricot'
|
11
12
|
autoload :Nokogiri, 'premailer/adapter/nokogiri'
|
13
|
+
autoload :Nokogumbo, 'premailer/adapter/nokogumbo'
|
12
14
|
|
13
15
|
# adapter to required file mapping.
|
14
16
|
REQUIREMENT_MAP = [
|
15
|
-
["hpricot", :hpricot],
|
16
17
|
["nokogiri", :nokogiri],
|
18
|
+
["nokogumbo", :nokogumbo],
|
19
|
+
["hpricot", :hpricot],
|
17
20
|
]
|
18
21
|
|
19
22
|
# Returns the adapter to use.
|
@@ -28,8 +31,9 @@ class Premailer
|
|
28
31
|
# then checks to see which are installed if none are loaded.
|
29
32
|
# @raise [RuntimeError] unless suitable adapter found.
|
30
33
|
def self.default
|
31
|
-
return :hpricot if defined?(::Hpricot)
|
32
34
|
return :nokogiri if defined?(::Nokogiri)
|
35
|
+
return :nokogumbo if defined?(::Nokogumbo)
|
36
|
+
return :hpricot if defined?(::Hpricot)
|
33
37
|
|
34
38
|
REQUIREMENT_MAP.each do |(library, adapter)|
|
35
39
|
begin
|
@@ -4,6 +4,13 @@ class Premailer
|
|
4
4
|
module Adapter
|
5
5
|
# Hpricot adapter
|
6
6
|
module Hpricot
|
7
|
+
def self.included(base)
|
8
|
+
warn <<eos
|
9
|
+
[DEPRECATED] Premailer's Hpricot adapter will be removed with the next major \
|
10
|
+
release. Change your :adapter option to :nokogiri or remove the `hpricot` gem \
|
11
|
+
from your application to silence this warning. (Called from #{caller[2]})
|
12
|
+
eos
|
13
|
+
end
|
7
14
|
|
8
15
|
# Merge CSS into the HTML document.
|
9
16
|
# @return [String] HTML.
|
@@ -75,12 +82,15 @@ class Premailer
|
|
75
82
|
# Perform style folding
|
76
83
|
merged = CssParser.merge(declarations)
|
77
84
|
merged.expand_shorthand!
|
78
|
-
merged.create_shorthand!
|
85
|
+
merged.create_shorthand! if @options[:create_shorthands]
|
79
86
|
|
80
87
|
# Duplicate CSS attributes as HTML attributes
|
81
|
-
if Premailer::RELATED_ATTRIBUTES.has_key?(el.name)
|
88
|
+
if Premailer::RELATED_ATTRIBUTES.has_key?(el.name) && @options[:css_to_attributes]
|
82
89
|
Premailer::RELATED_ATTRIBUTES[el.name].each do |css_att, html_att|
|
83
90
|
el[html_att] = merged[css_att].gsub(/url\('(.*)'\)/,'\1').gsub(/;$|\s*!important/, '').strip if el[html_att].nil? and not merged[css_att].empty?
|
91
|
+
merged.instance_variable_get("@declarations").tap do |declarations|
|
92
|
+
declarations.delete(css_att)
|
93
|
+
end
|
84
94
|
end
|
85
95
|
end
|
86
96
|
|
@@ -17,7 +17,6 @@ class Premailer
|
|
17
17
|
doc.search("*[@style]").each do |el|
|
18
18
|
el['style'] = '[SPEC=1000[' + el.attributes['style'] + ']]'
|
19
19
|
end
|
20
|
-
|
21
20
|
# Iterate through the rules and merge them into the HTML
|
22
21
|
@css_parser.each_selector(:all) do |selector, declaration, specificity, media_types|
|
23
22
|
# Save un-mergable rules separately
|
@@ -74,19 +73,22 @@ class Premailer
|
|
74
73
|
merged.expand_shorthand!
|
75
74
|
|
76
75
|
# Duplicate CSS attributes as HTML attributes
|
77
|
-
if Premailer::RELATED_ATTRIBUTES.has_key?(el.name)
|
76
|
+
if Premailer::RELATED_ATTRIBUTES.has_key?(el.name) && @options[:css_to_attributes]
|
78
77
|
Premailer::RELATED_ATTRIBUTES[el.name].each do |css_att, html_att|
|
79
78
|
el[html_att] = merged[css_att].gsub(/url\(['|"](.*)['|"]\)/, '\1').gsub(/;$|\s*!important/, '').strip if el[html_att].nil? and not merged[css_att].empty?
|
79
|
+
merged.instance_variable_get("@declarations").tap do |declarations|
|
80
|
+
declarations.delete(css_att)
|
81
|
+
end
|
80
82
|
end
|
81
83
|
end
|
82
|
-
|
83
84
|
# Collapse multiple rules into one as much as possible.
|
84
|
-
merged.create_shorthand!
|
85
|
+
merged.create_shorthand! if @options[:create_shorthands]
|
85
86
|
|
86
87
|
# write the inline STYLE attribute
|
87
|
-
|
88
|
+
# split by ';' but ignore those in brackets
|
89
|
+
attributes = Premailer.escape_string(merged.declarations_to_s).split(/;(?![^(]*\))/).map(&:strip)
|
88
90
|
attributes = attributes.map { |attr| [attr.split(':').first, attr] }.sort_by { |pair| pair.first }.map { |pair| pair[1] }
|
89
|
-
el['style'] = attributes.join('; ')
|
91
|
+
el['style'] = attributes.join('; ') + ";"
|
90
92
|
end
|
91
93
|
|
92
94
|
doc = write_unmergable_css_rules(doc, @unmergable_rules)
|
@@ -0,0 +1,243 @@
|
|
1
|
+
require 'nokogumbo'
|
2
|
+
|
3
|
+
class Premailer
|
4
|
+
module Adapter
|
5
|
+
# Nokogiri adapter
|
6
|
+
module Nokogumbo
|
7
|
+
|
8
|
+
# Merge CSS into the HTML document.
|
9
|
+
#
|
10
|
+
# @return [String] an HTML.
|
11
|
+
def to_inline_css
|
12
|
+
doc = @processed_doc
|
13
|
+
@unmergable_rules = CssParser::Parser.new
|
14
|
+
|
15
|
+
# Give all styles already in style attributes a specificity of 1000
|
16
|
+
# per http://www.w3.org/TR/CSS21/cascade.html#specificity
|
17
|
+
doc.search("*[@style]").each do |el|
|
18
|
+
el['style'] = '[SPEC=1000[' + el.attributes['style'] + ']]'
|
19
|
+
end
|
20
|
+
# Iterate through the rules and merge them into the HTML
|
21
|
+
@css_parser.each_selector(:all) do |selector, declaration, specificity, media_types|
|
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 Premailer.is_media_query?(media_types) || selector =~ Premailer::RE_UNMERGABLE_SELECTORS
|
29
|
+
@unmergable_rules.add_rule_set!(CssParser::RuleSet.new(selector, declaration), media_types) unless @options[:preserve_styles]
|
30
|
+
else
|
31
|
+
begin
|
32
|
+
if selector =~ Premailer::RE_RESET_SELECTORS
|
33
|
+
# this is in place to preserve the MailChimp CSS reset: http://github.com/mailchimp/Email-Blueprints/
|
34
|
+
# however, this doesn't mean for testing pur
|
35
|
+
@unmergable_rules.add_rule_set!(CssParser::RuleSet.new(selector, declaration)) unless !@options[:preserve_reset]
|
36
|
+
end
|
37
|
+
|
38
|
+
# Change single ID CSS selectors into xpath so that we can match more
|
39
|
+
# than one element. Added to work around dodgy generated code.
|
40
|
+
selector.gsub!(/\A\#([\w_\-]+)\Z/, '*[@id=\1]')
|
41
|
+
|
42
|
+
doc.search(selector).each do |el|
|
43
|
+
if el.elem? and (el.name != 'head' and el.parent.name != 'head')
|
44
|
+
# Add a style attribute or append to the existing one
|
45
|
+
block = "[SPEC=#{specificity}[#{declaration}]]"
|
46
|
+
el['style'] = (el.attributes['style'].to_s ||= '') + ' ' + block
|
47
|
+
end
|
48
|
+
end
|
49
|
+
rescue ::Nokogiri::SyntaxError, RuntimeError, ArgumentError
|
50
|
+
$stderr.puts "CSS syntax error with selector: #{selector}" if @options[:verbose]
|
51
|
+
next
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# Remove script tags
|
57
|
+
if @options[:remove_scripts]
|
58
|
+
doc.search("script").remove
|
59
|
+
end
|
60
|
+
|
61
|
+
# Read STYLE attributes and perform folding
|
62
|
+
doc.search("*[@style]").each do |el|
|
63
|
+
style = el.attributes['style'].to_s
|
64
|
+
|
65
|
+
declarations = []
|
66
|
+
style.scan(/\[SPEC\=([\d]+)\[(.[^\]\]]*)\]\]/).each do |declaration|
|
67
|
+
rs = CssParser::RuleSet.new(nil, declaration[1].to_s, declaration[0].to_i)
|
68
|
+
declarations << rs
|
69
|
+
end
|
70
|
+
|
71
|
+
# Perform style folding
|
72
|
+
merged = CssParser.merge(declarations)
|
73
|
+
merged.expand_shorthand!
|
74
|
+
|
75
|
+
# Duplicate CSS attributes as HTML attributes
|
76
|
+
if Premailer::RELATED_ATTRIBUTES.has_key?(el.name) && @options[:css_to_attributes]
|
77
|
+
Premailer::RELATED_ATTRIBUTES[el.name].each do |css_att, html_att|
|
78
|
+
el[html_att] = merged[css_att].gsub(/url\(['|"](.*)['|"]\)/, '\1').gsub(/;$|\s*!important/, '').strip if el[html_att].nil? and not merged[css_att].empty?
|
79
|
+
merged.instance_variable_get("@declarations").tap do |declarations|
|
80
|
+
declarations.delete(css_att)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
# Collapse multiple rules into one as much as possible.
|
85
|
+
merged.create_shorthand! if @options[:create_shorthands]
|
86
|
+
|
87
|
+
# write the inline STYLE attribute
|
88
|
+
attributes = Premailer.escape_string(merged.declarations_to_s).split(';').map(&:strip)
|
89
|
+
attributes = attributes.map { |attr| [attr.split(':').first, attr] }.sort_by { |pair| pair.first }.map { |pair| pair[1] }
|
90
|
+
el['style'] = attributes.join('; ') + ";"
|
91
|
+
end
|
92
|
+
|
93
|
+
doc = write_unmergable_css_rules(doc, @unmergable_rules)
|
94
|
+
|
95
|
+
if @options[:remove_classes] or @options[:remove_comments]
|
96
|
+
doc.traverse do |el|
|
97
|
+
if el.comment? and @options[:remove_comments]
|
98
|
+
el.remove
|
99
|
+
elsif el.element?
|
100
|
+
el.remove_attribute('class') if @options[:remove_classes]
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
if @options[:remove_ids]
|
106
|
+
# find all anchor's targets and hash them
|
107
|
+
targets = []
|
108
|
+
doc.search("a[@href^='#']").each do |el|
|
109
|
+
target = el.get_attribute('href')[1..-1]
|
110
|
+
targets << target
|
111
|
+
el.set_attribute('href', "#" + Digest::MD5.hexdigest(target))
|
112
|
+
end
|
113
|
+
# hash ids that are links target, delete others
|
114
|
+
doc.search("*[@id]").each do |el|
|
115
|
+
id = el.get_attribute('id')
|
116
|
+
if targets.include?(id)
|
117
|
+
el.set_attribute('id', Digest::MD5.hexdigest(id))
|
118
|
+
else
|
119
|
+
el.remove_attribute('id')
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
if @options[:reset_contenteditable]
|
125
|
+
doc.search('*[@contenteditable]').each do |el|
|
126
|
+
el.remove_attribute('contenteditable')
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
@processed_doc = doc
|
131
|
+
if is_xhtml?
|
132
|
+
# we don't want to encode carriage returns
|
133
|
+
@processed_doc.to_xhtml(:encoding => @options[:output_encoding]).gsub(/&\#(xD|13);/i, "\r")
|
134
|
+
else
|
135
|
+
@processed_doc.to_html(:encoding => @options[:output_encoding])
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
# Create a <tt>style</tt> element with un-mergable rules (e.g. <tt>:hover</tt>)
|
140
|
+
# and write it into the <tt>body</tt>.
|
141
|
+
#
|
142
|
+
# <tt>doc</tt> is an Nokogiri document and <tt>unmergable_css_rules</tt> is a Css::RuleSet.
|
143
|
+
#
|
144
|
+
# @return [::Nokogiri::XML] a document.
|
145
|
+
def write_unmergable_css_rules(doc, unmergable_rules) # :nodoc:
|
146
|
+
styles = unmergable_rules.to_s
|
147
|
+
|
148
|
+
unless styles.empty?
|
149
|
+
style_tag = "<style type=\"text/css\">\n#{styles}</style>"
|
150
|
+
unless (body = doc.search('body')).empty?
|
151
|
+
if doc.at_css('body').children && !doc.at_css('body').children.empty?
|
152
|
+
doc.at_css('body').children.before(::Nokogiri::XML.fragment(style_tag))
|
153
|
+
else
|
154
|
+
doc.at_css('body').add_child(::Nokogiri::XML.fragment(style_tag))
|
155
|
+
end
|
156
|
+
else
|
157
|
+
doc.inner_html = style_tag += doc.inner_html
|
158
|
+
end
|
159
|
+
end
|
160
|
+
doc
|
161
|
+
end
|
162
|
+
|
163
|
+
|
164
|
+
# Converts the HTML document to a format suitable for plain-text e-mail.
|
165
|
+
#
|
166
|
+
# If present, uses the <body> element as its base; otherwise uses the whole document.
|
167
|
+
#
|
168
|
+
# @return [String] a plain text.
|
169
|
+
def to_plain_text
|
170
|
+
html_src = ''
|
171
|
+
begin
|
172
|
+
html_src = @doc.at("body").inner_html
|
173
|
+
rescue;
|
174
|
+
end
|
175
|
+
|
176
|
+
html_src = @doc.to_html unless html_src and not html_src.empty?
|
177
|
+
convert_to_text(html_src, @options[:line_length], @html_encoding)
|
178
|
+
end
|
179
|
+
|
180
|
+
# Gets the original HTML as a string.
|
181
|
+
# @return [String] HTML.
|
182
|
+
def to_s
|
183
|
+
if is_xhtml?
|
184
|
+
@doc.to_xhtml(:encoding => nil)
|
185
|
+
else
|
186
|
+
@doc.to_html(:encoding => nil)
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
# Load the HTML file and convert it into an Nokogiri document.
|
191
|
+
#
|
192
|
+
# @return [::Nokogiri::XML] a document.
|
193
|
+
def load_html(input) # :nodoc:
|
194
|
+
thing = nil
|
195
|
+
|
196
|
+
# TODO: duplicate options
|
197
|
+
if @options[:with_html_string] or @options[:inline] or input.respond_to?(:read)
|
198
|
+
thing = input
|
199
|
+
elsif @is_local_file
|
200
|
+
@base_dir = File.dirname(input)
|
201
|
+
thing = File.open(input, 'r')
|
202
|
+
else
|
203
|
+
thing = open(input)
|
204
|
+
end
|
205
|
+
|
206
|
+
if thing.respond_to?(:read)
|
207
|
+
thing = thing.read
|
208
|
+
end
|
209
|
+
|
210
|
+
return nil unless thing
|
211
|
+
doc = nil
|
212
|
+
|
213
|
+
# Handle HTML entities
|
214
|
+
if @options[:replace_html_entities] == true and thing.is_a?(String)
|
215
|
+
HTML_ENTITIES.map do |entity, replacement|
|
216
|
+
thing.gsub! entity, replacement
|
217
|
+
end
|
218
|
+
end
|
219
|
+
# Default encoding is ASCII-8BIT (binary) per http://groups.google.com/group/nokogiri-talk/msg/0b81ef0dc180dc74
|
220
|
+
# However, we really don't want to hardcode this. ASCII-8BIT should be the default, but not the only option.
|
221
|
+
if thing.is_a?(String) and RUBY_VERSION =~ /1.9/
|
222
|
+
thing = thing.force_encoding(@options[:input_encoding]).encode!
|
223
|
+
doc = ::Nokogiri::HTML5(thing)
|
224
|
+
else
|
225
|
+
default_encoding = RUBY_PLATFORM == 'java' ? nil : 'BINARY'
|
226
|
+
doc = ::Nokogiri::HTML5(thing)
|
227
|
+
end
|
228
|
+
|
229
|
+
# Fix for removing any CDATA tags from both style and script tags inserted per
|
230
|
+
# https://github.com/sparklemotion/nokogiri/issues/311 and
|
231
|
+
# https://github.com/premailer/premailer/issues/199
|
232
|
+
%w(style script).each do |tag|
|
233
|
+
doc.search(tag).children.each do |child|
|
234
|
+
child.swap(child.text()) if child.cdata?
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
doc
|
239
|
+
end
|
240
|
+
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
@@ -31,17 +31,11 @@ module HtmlToPlainText
|
|
31
31
|
txt.gsub!(/<img.+?alt=\'([^\']*)\'[^>]*\>/i, '\1')
|
32
32
|
|
33
33
|
# links
|
34
|
-
txt.gsub!(/<a\s.*?href
|
35
|
-
if $3.empty?
|
36
|
-
''
|
37
|
-
else
|
38
|
-
$3.strip + ' ( ' + $2.strip + ' )'
|
39
|
-
end
|
40
|
-
end
|
41
|
-
|
42
|
-
txt.gsub!(/<a\s.*?href='(mailto:)?([^\']*)\'[^>]*>((.|\s)*?)<\/a>/i) do |s|
|
34
|
+
txt.gsub!(/<a\s.*?href=["'](mailto:)?([^"']*)["'][^>]*>((.|\s)*?)<\/a>/i) do |s|
|
43
35
|
if $3.empty?
|
44
36
|
''
|
37
|
+
elsif $3.strip.downcase == $2.strip.downcase
|
38
|
+
$3.strip
|
45
39
|
else
|
46
40
|
$3.strip + ' ( ' + $2.strip + ' )'
|
47
41
|
end
|
@@ -92,25 +86,25 @@ module HtmlToPlainText
|
|
92
86
|
he = HTMLEntities.new
|
93
87
|
txt = he.decode(txt)
|
94
88
|
|
89
|
+
# no more than two consecutive spaces
|
90
|
+
txt.gsub!(/ {2,}/, " ")
|
91
|
+
|
95
92
|
txt = word_wrap(txt, line_length)
|
96
93
|
|
97
94
|
# remove linefeeds (\r\n and \r -> \n)
|
98
95
|
txt.gsub!(/\r\n?/, "\n")
|
99
96
|
|
100
97
|
# strip extra spaces
|
101
|
-
txt.gsub!(
|
98
|
+
txt.gsub!(/[ \t]*\302\240+[ \t]*/, " ") # non-breaking spaces -> spaces
|
102
99
|
txt.gsub!(/\n[ \t]+/, "\n") # space at start of lines
|
103
100
|
txt.gsub!(/[ \t]+\n/, "\n") # space at end of lines
|
104
101
|
|
105
102
|
# no more than two consecutive newlines
|
106
103
|
txt.gsub!(/[\n]{3,}/, "\n\n")
|
107
104
|
|
108
|
-
# no more than two consecutive spaces
|
109
|
-
txt.gsub!(/ {2,}/, " ")
|
110
|
-
|
111
105
|
# the word messes up the parens
|
112
|
-
txt.gsub!(/\([ \n](http[^)]+)[\n ]\)/) do |s|
|
113
|
-
|
106
|
+
txt.gsub!(/\(([ \n])(http[^)]+)([\n ])\)/) do |s|
|
107
|
+
($1 == "\n" ? $1 : '' ) + '( ' + $2 + ' )' + ($3 == "\n" ? $1 : '' )
|
114
108
|
end
|
115
109
|
|
116
110
|
txt.strip
|
data/lib/premailer/premailer.rb
CHANGED
@@ -166,6 +166,7 @@ class Premailer
|
|
166
166
|
# @option options [Boolean] :preserve_reset Whether to preserve styles associated with the MailChimp reset code. Default is true.
|
167
167
|
# @option options [Boolean] :with_html_string Whether the html param should be treated as a raw string. Default is false.
|
168
168
|
# @option options [Boolean] :verbose Whether to print errors and warnings to <tt>$stderr</tt>. Default is false.
|
169
|
+
# @option options [Boolean] :io_exceptions Throws exceptions on I/O errors.
|
169
170
|
# @option options [Boolean] :include_link_tags Whether to include css from <tt>link rel=stylesheet</tt> tags. Default is true.
|
170
171
|
# @option options [Boolean] :include_style_tags Whether to include css from <tt>style</tt> tags. Default is true.
|
171
172
|
# @option options [String] :input_encoding Manually specify the source documents encoding. This is a good idea. Default is ASCII-8BIT.
|
@@ -173,6 +174,7 @@ class Premailer
|
|
173
174
|
# @option options [Boolean] :escape_url_attributes URL Escapes href, src, and background attributes on elements. Default is true.
|
174
175
|
# @option options [Symbol] :adapter Which HTML parser to use, either <tt>:nokogiri</tt> or <tt>:hpricot</tt>. Default is <tt>:hpricot</tt>.
|
175
176
|
# @option options [String] :output_encoding Output encoding option for Nokogiri adapter. Should be set to "US-ASCII" to output HTML entities instead of Unicode characters.
|
177
|
+
# @option options [Boolean] :create_shorthands Combine several properties into a shorthand one, e.g. font: style weight size. Default is true.
|
176
178
|
def initialize(html, options = {})
|
177
179
|
@options = {:warn_level => Warnings::SAFE,
|
178
180
|
:line_length => 65,
|
@@ -199,6 +201,7 @@ class Premailer
|
|
199
201
|
:replace_html_entities => false,
|
200
202
|
:escape_url_attributes => true,
|
201
203
|
:unescaped_ampersand => false,
|
204
|
+
:create_shorthands => true,
|
202
205
|
:adapter => Adapter.use,
|
203
206
|
}.merge(options)
|
204
207
|
|
@@ -260,7 +263,9 @@ protected
|
|
260
263
|
end
|
261
264
|
|
262
265
|
load_css_from_string(css_block)
|
263
|
-
rescue
|
266
|
+
rescue => e
|
267
|
+
raise e if @options[:io_exceptions]
|
268
|
+
end
|
264
269
|
end
|
265
270
|
|
266
271
|
def load_css_from_string(css_string)
|
@@ -282,11 +287,7 @@ protected
|
|
282
287
|
|
283
288
|
# Load CSS included in <tt>style</tt> and <tt>link</tt> tags from an HTML document.
|
284
289
|
def load_css_from_html! # :nodoc:
|
285
|
-
|
286
|
-
tags = @doc.search("link[@rel='stylesheet']:not([@data-premailer='ignore'])", "//style[not(contains(@data-premailer,'ignore'))]")
|
287
|
-
else
|
288
|
-
tags = @doc.search("link[@rel='stylesheet']:not([@data-premailer='ignore']), style:not([@data-premailer='ignore'])")
|
289
|
-
end
|
290
|
+
tags = @doc.search("link[@rel='stylesheet']:not([@data-premailer='ignore']), style:not([@data-premailer='ignore'])")
|
290
291
|
if tags
|
291
292
|
tags.each do |tag|
|
292
293
|
if tag.to_s.strip =~ /^\<link/i && tag.attributes['href'] && media_type_ok?(tag.attributes['media']) && @options[:include_link_tags]
|
@@ -298,10 +299,10 @@ protected
|
|
298
299
|
link_uri = tag.attributes['href'].to_s.sub(@base_url.to_s, '')
|
299
300
|
else
|
300
301
|
link_uri = File.join(File.dirname(@html_file), tag.attributes['href'].to_s.sub!(@base_url.to_s, ''))
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
302
|
+
end
|
303
|
+
# if the file does not exist locally, try to grab the remote reference
|
304
|
+
unless File.exists?(link_uri)
|
305
|
+
link_uri = Premailer.resolve_link(tag.attributes['href'].to_s, @html_file)
|
305
306
|
end
|
306
307
|
else
|
307
308
|
link_uri = tag.attributes['href'].to_s
|