premailer 1.7.1 → 1.7.3
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +7 -0
- data/Gemfile +3 -0
- data/LICENSE.rdoc +11 -0
- data/README.rdoc +20 -0
- data/bin/premailer +3 -95
- data/lib/premailer.rb +5 -3
- data/lib/premailer/adapter/hpricot.rb +21 -11
- data/lib/premailer/adapter/nokogiri.rb +18 -17
- data/lib/premailer/executor.rb +96 -0
- data/lib/premailer/html_to_plain_text.rb +26 -6
- data/lib/premailer/premailer.rb +62 -32
- data/local-premailer +9 -0
- data/premailer.gemspec +22 -0
- data/rakefile.rb +69 -0
- data/test/files/base.html +142 -0
- data/test/files/chars.html +6 -0
- 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 +12 -0
- data/test/files/import.css +13 -0
- data/test/files/inc/2009-placeholder.png +0 -0
- data/test/files/iso-8859-2.html +1 -0
- data/test/files/iso-8859-5.html +8 -0
- data/test/files/no_css.html +11 -0
- data/test/files/noimport.css +13 -0
- data/test/files/styles.css +106 -0
- data/test/files/xhtml.html +11 -0
- data/test/future_tests.rb +50 -0
- data/test/helper.rb +7 -0
- data/test/test_adapter.rb +29 -0
- data/test/test_html_to_plain_text.rb +149 -0
- data/test/test_links.rb +141 -0
- data/test/test_misc.rb +233 -0
- data/test/test_premailer.rb +257 -0
- data/test/test_warnings.rb +97 -0
- metadata +109 -13
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.rdoc
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
= Premailer License
|
2
|
+
|
3
|
+
Copyright (c) 2007-2011, Alex Dunae. All rights reserved.
|
4
|
+
|
5
|
+
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
6
|
+
|
7
|
+
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
8
|
+
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
9
|
+
* Neither the name of Premailer, Alex Dunae nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
10
|
+
|
11
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/README.rdoc
CHANGED
@@ -42,6 +42,26 @@ Download the Premailer gem from RubyGems.
|
|
42
42
|
premailer.warnings.each do |w|
|
43
43
|
puts "#{w[:message]} (#{w[:level]}) may not render properly in #{w[:clients]}"
|
44
44
|
end
|
45
|
+
|
46
|
+
=== Premailer-specific CSS
|
47
|
+
|
48
|
+
Premailer looks for a few CSS attributes that make working with tables a bit easier.
|
49
|
+
|
50
|
+
+-premailer-width+:: Available on <tt>table</tt>, <tt>th</tt> and <tt>td</tt> elements
|
51
|
+
+-premailer-height+:: Available on <tt>table</tt>, <tt>tr</tt>, <tt>th</tt> and <tt>td</tt> elements
|
52
|
+
+-premailer-cellpadding+:: Available on <tt>table</tt> elements
|
53
|
+
+-premailer-cellspacing+:: Available on <tt>table</tt> elements
|
54
|
+
|
55
|
+
Each of these CSS declarations will be copied to appropriate element's attribute.
|
56
|
+
|
57
|
+
For example
|
58
|
+
|
59
|
+
table { -premailer-cellspacing: 5; -premailer-width: 500;}
|
60
|
+
|
61
|
+
will result in
|
62
|
+
|
63
|
+
<table cellspacing='5' width='500'>
|
64
|
+
|
45
65
|
|
46
66
|
=== Contributions
|
47
67
|
|
data/bin/premailer
CHANGED
@@ -1,99 +1,7 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
|
4
|
-
require 'rubygems'
|
5
|
-
require File.expand_path(File.join(__FILE__) + '/../../lib/premailer.rb')
|
6
|
-
|
7
|
-
# defaults
|
8
|
-
options = {
|
9
|
-
:base_url => nil,
|
10
|
-
:link_query_string => nil,
|
11
|
-
:remove_classes => false,
|
12
|
-
:verbose => false,
|
13
|
-
:line_length => 65
|
14
|
-
}
|
15
|
-
|
16
|
-
mode = :html
|
17
|
-
|
18
|
-
opts = OptionParser.new do |opts|
|
19
|
-
opts.banner = "Improve the rendering of HTML emails by making CSS inline among other things. Takes a path to a local file, a URL or a pipe as input.\n\n"
|
20
|
-
opts.define_head "Usage: premailer <optional uri|optional path> [options]"
|
21
|
-
opts.separator ""
|
22
|
-
opts.separator "Examples:"
|
23
|
-
opts.separator " premailer http://example.com/ > out.html"
|
24
|
-
opts.separator " premailer http://example.com/ --mode txt > out.txt"
|
25
|
-
opts.separator " cat input.html | premailer -q src=email > out.html"
|
26
|
-
opts.separator " premailer ./public/index.html"
|
27
|
-
opts.separator ""
|
28
|
-
opts.separator "Options:"
|
29
|
-
|
30
|
-
opts.on("--mode MODE", [:html, :txt], "Output: html or txt") do |v|
|
31
|
-
mode = v
|
32
|
-
end
|
33
|
-
|
34
|
-
opts.on("-b", "--base-url STRING", String, "Base URL, useful for local files") do |v|
|
35
|
-
options[:base_url] = v
|
36
|
-
end
|
37
|
-
|
38
|
-
opts.on("-q", "--query-string STRING", String, "Query string to append to links") do |v|
|
39
|
-
options[:link_query_string] = v
|
40
|
-
end
|
41
|
-
|
42
|
-
opts.on("--css FILE,FILE", Array, "Additional CSS stylesheets") do |v|
|
43
|
-
options[:css] = v
|
44
|
-
end
|
45
|
-
|
46
|
-
opts.on("-r", "--remove-classes", "Remove HTML classes") do |v|
|
47
|
-
options[:remove_classes] = v
|
48
|
-
end
|
49
|
-
|
50
|
-
opts.on("-l", "--line-length N", Integer, "Line length for plaintext (default: #{options[:line_length].to_s})") do |v|
|
51
|
-
options[:line_length] = v
|
52
|
-
end
|
3
|
+
# This binary used in rubygems environment only as part of installed gem
|
53
4
|
|
54
|
-
|
55
|
-
|
56
|
-
end
|
57
|
-
|
58
|
-
opts.on("-v", "--verbose", "Print additional information at runtime") do |v|
|
59
|
-
options[:verbose] = v
|
60
|
-
end
|
61
|
-
|
62
|
-
opts.on_tail("-?", "--help", "Show this message") do
|
63
|
-
puts opts
|
64
|
-
exit
|
65
|
-
end
|
66
|
-
|
67
|
-
opts.on_tail("-V", "--version", "Show version") do
|
68
|
-
puts "Premailer #{Premailer::VERSION} (c) 2008-2010 Alex Dunae"
|
69
|
-
exit
|
70
|
-
end
|
71
|
-
end
|
72
|
-
opts.parse!
|
73
|
-
|
74
|
-
$stderr.puts "Processing in #{mode} mode with options #{options.inspect}" if options[:verbose]
|
75
|
-
|
76
|
-
premailer = nil
|
77
|
-
input = nil
|
78
|
-
|
79
|
-
if $stdin.tty?
|
80
|
-
input = ARGV.shift
|
81
|
-
else
|
82
|
-
input = $stdin
|
83
|
-
options[:with_html_string] = true
|
84
|
-
end
|
85
|
-
|
86
|
-
if input
|
87
|
-
premailer = Premailer.new(input, options)
|
88
|
-
else
|
89
|
-
puts opts
|
90
|
-
exit 1
|
91
|
-
end
|
92
|
-
|
93
|
-
if mode == :txt
|
94
|
-
print premailer.to_plain_text
|
95
|
-
else
|
96
|
-
print premailer.to_inline_css
|
97
|
-
end
|
5
|
+
require 'rubygems'
|
6
|
+
require 'premailer/executor'
|
98
7
|
|
99
|
-
exit
|
data/lib/premailer.rb
CHANGED
@@ -3,6 +3,8 @@ require 'open-uri'
|
|
3
3
|
require 'digest/md5'
|
4
4
|
require 'cgi'
|
5
5
|
require 'css_parser'
|
6
|
-
|
7
|
-
require
|
8
|
-
require
|
6
|
+
|
7
|
+
require 'premailer/adapter'
|
8
|
+
require 'premailer/html_to_plain_text'
|
9
|
+
require 'premailer/premailer'
|
10
|
+
|
@@ -29,9 +29,19 @@ class Premailer
|
|
29
29
|
@unmergable_rules.add_rule_set!(CssParser::RuleSet.new(selector, declaration)) unless @options[:preserve_styles]
|
30
30
|
else
|
31
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
|
+
|
32
38
|
# Change single ID CSS selectors into xpath so that we can match more
|
33
39
|
# than one element. Added to work around dodgy generated code.
|
34
40
|
selector.gsub!(/\A\#([\w_\-]+)\Z/, '*[@id=\1]')
|
41
|
+
|
42
|
+
# convert attribute selectors to hpricot's format
|
43
|
+
selector.gsub!(/\[([\w]+)\]/, '[@\1]')
|
44
|
+
selector.gsub!(/\[([\w]+)([\=\~\^\$\*]+)([\w\s]+)\]/, '[@\1\2\'\3\']')
|
35
45
|
|
36
46
|
doc.search(selector).each do |el|
|
37
47
|
if el.elem? and (el.name != 'head' and el.parent.name != 'head')
|
@@ -57,7 +67,6 @@ class Premailer
|
|
57
67
|
rs = CssParser::RuleSet.new(nil, declaration[1].to_s, declaration[0].to_i)
|
58
68
|
declarations << rs
|
59
69
|
end
|
60
|
-
|
61
70
|
# Perform style folding
|
62
71
|
merged = CssParser.merge(declarations)
|
63
72
|
merged.expand_shorthand!
|
@@ -70,6 +79,7 @@ class Premailer
|
|
70
79
|
end
|
71
80
|
|
72
81
|
merged.create_dimensions_shorthand!
|
82
|
+
merged.create_border_shorthand!
|
73
83
|
|
74
84
|
# write the inline STYLE attribute
|
75
85
|
el['style'] = Premailer.escape_string(merged.declarations_to_s)
|
@@ -120,18 +130,18 @@ class Premailer
|
|
120
130
|
#
|
121
131
|
# Returns an Hpricot document.
|
122
132
|
def write_unmergable_css_rules(doc, unmergable_rules) # :nodoc:
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
end
|
133
|
+
styles = ''
|
134
|
+
unmergable_rules.each_selector(:all, :force_important => true) do |selector, declarations, specificity|
|
135
|
+
styles += "#{selector} { #{declarations} }\n"
|
136
|
+
end
|
128
137
|
|
129
|
-
|
130
|
-
|
131
|
-
|
138
|
+
unless styles.empty?
|
139
|
+
style_tag = "\n<style type=\"text/css\">\n#{styles}</style>\n"
|
140
|
+
if body = doc.search('body')
|
141
|
+
body.append(style_tag)
|
142
|
+
else
|
143
|
+
doc.inner_html= doc.inner_html << style_tag
|
132
144
|
end
|
133
|
-
else
|
134
|
-
$stderr.puts "Unable to write unmergable CSS rules: no <head> was found" if @options[:verbose]
|
135
145
|
end
|
136
146
|
doc
|
137
147
|
end
|
@@ -52,7 +52,6 @@ class Premailer
|
|
52
52
|
style = el.attributes['style'].to_s
|
53
53
|
|
54
54
|
declarations = []
|
55
|
-
|
56
55
|
style.scan(/\[SPEC\=([\d]+)\[(.[^\]\]]*)\]\]/).each do |declaration|
|
57
56
|
rs = CssParser::RuleSet.new(nil, declaration[1].to_s, declaration[0].to_i)
|
58
57
|
declarations << rs
|
@@ -70,6 +69,7 @@ class Premailer
|
|
70
69
|
end
|
71
70
|
|
72
71
|
merged.create_dimensions_shorthand!
|
72
|
+
merged.create_border_shorthand!
|
73
73
|
|
74
74
|
# write the inline STYLE attribute
|
75
75
|
el['style'] = Premailer.escape_string(merged.declarations_to_s)
|
@@ -108,7 +108,8 @@ class Premailer
|
|
108
108
|
|
109
109
|
@processed_doc = doc
|
110
110
|
if is_xhtml?
|
111
|
-
|
111
|
+
# we don't want to encode carriage returns
|
112
|
+
@processed_doc.to_xhtml(:encoding => nil).gsub(/&\#xD;/i, "\r")
|
112
113
|
else
|
113
114
|
@processed_doc.to_html
|
114
115
|
end
|
@@ -121,20 +122,20 @@ class Premailer
|
|
121
122
|
#
|
122
123
|
# Returns an Nokogiri document.
|
123
124
|
def write_unmergable_css_rules(doc, unmergable_rules) # :nodoc:
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
end
|
129
|
-
|
130
|
-
unless styles.empty?
|
131
|
-
style_tag = "\n<style type=\"text/css\">\n#{styles}</style>\n"
|
125
|
+
styles = ''
|
126
|
+
unmergable_rules.each_selector(:all, :force_important => true) do |selector, declarations, specificity|
|
127
|
+
styles += "#{selector} { #{declarations} }\n"
|
128
|
+
end
|
132
129
|
|
133
|
-
|
130
|
+
unless styles.empty?
|
131
|
+
style_tag = "<style type=\"text/css\">\n#{styles}></style>"
|
132
|
+
if body = doc.search('body')
|
133
|
+
doc.at_css('body').children.first.before(style_tag)
|
134
|
+
else
|
135
|
+
doc.inner_html = style_tag += doc.inner_html
|
134
136
|
end
|
135
|
-
else
|
136
|
-
$stderr.puts "Unable to write unmergable CSS rules: no <head> was found" if @options[:verbose]
|
137
137
|
end
|
138
|
+
|
138
139
|
doc
|
139
140
|
end
|
140
141
|
|
@@ -157,9 +158,9 @@ class Premailer
|
|
157
158
|
# Returns the original HTML as a string.
|
158
159
|
def to_s
|
159
160
|
if is_xhtml?
|
160
|
-
@doc.to_xhtml
|
161
|
+
@doc.to_xhtml(:encoding => nil)
|
161
162
|
else
|
162
|
-
@doc.to_html
|
163
|
+
@doc.to_html(:encoding => nil)
|
163
164
|
end
|
164
165
|
end
|
165
166
|
|
@@ -190,9 +191,9 @@ class Premailer
|
|
190
191
|
# Default encoding is ASCII-8BIT (binary) per http://groups.google.com/group/nokogiri-talk/msg/0b81ef0dc180dc74
|
191
192
|
if thing.is_a?(String) and RUBY_VERSION =~ /1.9/
|
192
193
|
thing = thing.force_encoding('ASCII-8BIT').encode!
|
193
|
-
doc = ::Nokogiri::HTML(thing) {|c| c.
|
194
|
+
doc = ::Nokogiri::HTML(thing) {|c| c.recover }
|
194
195
|
else
|
195
|
-
doc = ::Nokogiri::HTML(thing, nil, '
|
196
|
+
doc = ::Nokogiri::HTML(thing, nil, @options[:inputencoding] || 'BINARY') {|c| c.recover }
|
196
197
|
end
|
197
198
|
|
198
199
|
return doc
|
@@ -0,0 +1,96 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
require 'premailer'
|
3
|
+
|
4
|
+
# defaults
|
5
|
+
options = {
|
6
|
+
:base_url => nil,
|
7
|
+
:link_query_string => nil,
|
8
|
+
:remove_classes => false,
|
9
|
+
:verbose => false,
|
10
|
+
:line_length => 65
|
11
|
+
}
|
12
|
+
|
13
|
+
mode = :html
|
14
|
+
|
15
|
+
opts = OptionParser.new do |opts|
|
16
|
+
opts.banner = "Improve the rendering of HTML emails by making CSS inline among other things. Takes a path to a local file, a URL or a pipe as input.\n\n"
|
17
|
+
opts.define_head "Usage: premailer <optional uri|optional path> [options]"
|
18
|
+
opts.separator ""
|
19
|
+
opts.separator "Examples:"
|
20
|
+
opts.separator " premailer http://example.com/ > out.html"
|
21
|
+
opts.separator " premailer http://example.com/ --mode txt > out.txt"
|
22
|
+
opts.separator " cat input.html | premailer -q src=email > out.html"
|
23
|
+
opts.separator " premailer ./public/index.html"
|
24
|
+
opts.separator ""
|
25
|
+
opts.separator "Options:"
|
26
|
+
|
27
|
+
opts.on("--mode MODE", [:html, :txt], "Output: html or txt") do |v|
|
28
|
+
mode = v
|
29
|
+
end
|
30
|
+
|
31
|
+
opts.on("-b", "--base-url STRING", String, "Base URL, useful for local files") do |v|
|
32
|
+
options[:base_url] = v
|
33
|
+
end
|
34
|
+
|
35
|
+
opts.on("-q", "--query-string STRING", String, "Query string to append to links") do |v|
|
36
|
+
options[:link_query_string] = v
|
37
|
+
end
|
38
|
+
|
39
|
+
opts.on("--css FILE,FILE", Array, "Additional CSS stylesheets") do |v|
|
40
|
+
options[:css] = v
|
41
|
+
end
|
42
|
+
|
43
|
+
opts.on("-r", "--remove-classes", "Remove HTML classes") do |v|
|
44
|
+
options[:remove_classes] = v
|
45
|
+
end
|
46
|
+
|
47
|
+
opts.on("-l", "--line-length N", Integer, "Line length for plaintext (default: #{options[:line_length].to_s})") do |v|
|
48
|
+
options[:line_length] = v
|
49
|
+
end
|
50
|
+
|
51
|
+
opts.on("-d", "--io-exceptions", "Abort on I/O errors") do |v|
|
52
|
+
options[:io_exceptions] = v
|
53
|
+
end
|
54
|
+
|
55
|
+
opts.on("-v", "--verbose", "Print additional information at runtime") do |v|
|
56
|
+
options[:verbose] = v
|
57
|
+
end
|
58
|
+
|
59
|
+
opts.on_tail("-?", "--help", "Show this message") do
|
60
|
+
puts opts
|
61
|
+
exit
|
62
|
+
end
|
63
|
+
|
64
|
+
opts.on_tail("-V", "--version", "Show version") do
|
65
|
+
puts "Premailer #{Premailer::VERSION} (c) 2008-2010 Alex Dunae"
|
66
|
+
exit
|
67
|
+
end
|
68
|
+
end
|
69
|
+
opts.parse!
|
70
|
+
|
71
|
+
$stderr.puts "Processing in #{mode} mode with options #{options.inspect}" if options[:verbose]
|
72
|
+
|
73
|
+
premailer = nil
|
74
|
+
input = nil
|
75
|
+
|
76
|
+
if $stdin.tty? or STDIN.fcntl(Fcntl::F_GETFL, 0) == 0
|
77
|
+
input = ARGV.shift
|
78
|
+
else
|
79
|
+
input = $stdin
|
80
|
+
options[:with_html_string] = true
|
81
|
+
end
|
82
|
+
|
83
|
+
if input
|
84
|
+
premailer = Premailer.new(input, options)
|
85
|
+
else
|
86
|
+
puts opts
|
87
|
+
exit 1
|
88
|
+
end
|
89
|
+
|
90
|
+
if mode == :txt
|
91
|
+
print premailer.to_plain_text
|
92
|
+
else
|
93
|
+
print premailer.to_inline_css
|
94
|
+
end
|
95
|
+
|
96
|
+
exit
|
@@ -13,6 +13,23 @@ module HtmlToPlainText
|
|
13
13
|
# decode HTML entities
|
14
14
|
he = HTMLEntities.new
|
15
15
|
txt = he.decode(txt)
|
16
|
+
|
17
|
+
# replace image by their alt attribute
|
18
|
+
txt.gsub!(/<img.+?alt=\"([^\"]*)\"[^>]*\/>/i, '\1')
|
19
|
+
|
20
|
+
# replace image by their alt attribute
|
21
|
+
txt.gsub!(/<img.+?alt=\"([^\"]*)\"[^>]*\/>/i, '\1')
|
22
|
+
txt.gsub!(/<img.+?alt='([^\']*)\'[^>]*\/>/i, '\1')
|
23
|
+
|
24
|
+
# links
|
25
|
+
txt.gsub!(/<a.+?href=\"([^\"]*)\"[^>]*>(.+?)<\/a>/i) do |s|
|
26
|
+
$2.strip + ' ( ' + $1.strip + ' )'
|
27
|
+
end
|
28
|
+
|
29
|
+
txt.gsub!(/<a.+?href='([^\']*)\'[^>]*>(.+?)<\/a>/i) do |s|
|
30
|
+
$2.strip + ' ( ' + $1.strip + ' )'
|
31
|
+
end
|
32
|
+
|
16
33
|
|
17
34
|
# handle headings (H1-H6)
|
18
35
|
txt.gsub!(/(<\/h[1-6]>)/i, "\n\\1") # move closing tags to new lines
|
@@ -43,11 +60,6 @@ module HtmlToPlainText
|
|
43
60
|
# wrap spans
|
44
61
|
txt.gsub!(/(<\/span>)[\s]+(<span)/mi, '\1 \2')
|
45
62
|
|
46
|
-
# links
|
47
|
-
txt.gsub!(/<a.*href=\"([^\"]*)\"[^>]*>(.*)<\/a>/i) do |s|
|
48
|
-
$2.strip + ' ( ' + $1.strip + ' )'
|
49
|
-
end
|
50
|
-
|
51
63
|
# lists -- TODO: should handle ordered lists
|
52
64
|
txt.gsub!(/[\s]*(<li[^>]*>)[\s]*/i, '* ')
|
53
65
|
# list not followed by a newline
|
@@ -61,7 +73,7 @@ module HtmlToPlainText
|
|
61
73
|
txt.gsub!(/<\/?[^>]*>/, '')
|
62
74
|
|
63
75
|
txt = word_wrap(txt, line_length)
|
64
|
-
|
76
|
+
|
65
77
|
# remove linefeeds (\r\n and \r -> \n)
|
66
78
|
txt.gsub!(/\r\n?/, "\n")
|
67
79
|
|
@@ -73,6 +85,14 @@ module HtmlToPlainText
|
|
73
85
|
# no more than two consecutive newlines
|
74
86
|
txt.gsub!(/[\n]{3,}/, "\n\n")
|
75
87
|
|
88
|
+
# no more than two consecutive spaces
|
89
|
+
txt.gsub!(/ {2,}/, " ")
|
90
|
+
|
91
|
+
# the word messes up the parens
|
92
|
+
txt.gsub!(/\([ \n](http[^)]+)[\n ]\)/) do |s|
|
93
|
+
"( " + $1 + " )"
|
94
|
+
end
|
95
|
+
|
76
96
|
txt.strip
|
77
97
|
end
|
78
98
|
|