premailer 1.8.6 → 1.8.7
Sign up to get free protection for your applications and to get access to all the features.
- 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
|