sterile 1.0.0
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.
- data/.autotest +5 -0
- data/.gitignore +8 -0
- data/.rvmrc +1 -0
- data/Gemfile +9 -0
- data/Gemfile.lock +14 -0
- data/README.markdown +0 -0
- data/Rakefile +7 -0
- data/lib/sterile/codepoints.rb +46522 -0
- data/lib/sterile/html_entities.rb +259 -0
- data/lib/sterile/smart_format_rules.rb +40 -0
- data/lib/sterile/version.rb +5 -0
- data/lib/sterile.rb +339 -0
- data/sterilize.gemspec +24 -0
- metadata +69 -0
@@ -0,0 +1,259 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module Sterile
|
4
|
+
HTML_ENTITIES = {
|
5
|
+
"quot" => 34,
|
6
|
+
"amp" => 38,
|
7
|
+
"apos" => 39,
|
8
|
+
"lt" => 60,
|
9
|
+
"gt" => 62,
|
10
|
+
"nbsp" => 160,
|
11
|
+
"iexcl" => 161,
|
12
|
+
"cent" => 162,
|
13
|
+
"pound" => 163,
|
14
|
+
"curren" => 164,
|
15
|
+
"yen" => 165,
|
16
|
+
"brvbar" => 166,
|
17
|
+
"sect" => 167,
|
18
|
+
"uml" => 168,
|
19
|
+
"copy" => 169,
|
20
|
+
"ordf" => 170,
|
21
|
+
"laquo" => 171,
|
22
|
+
"not" => 172,
|
23
|
+
"shy" => 173,
|
24
|
+
"reg" => 174,
|
25
|
+
"macr" => 175,
|
26
|
+
"deg" => 176,
|
27
|
+
"plusmn" => 177,
|
28
|
+
"sup2" => 178,
|
29
|
+
"sup3" => 179,
|
30
|
+
"acute" => 180,
|
31
|
+
"micro" => 181,
|
32
|
+
"para" => 182,
|
33
|
+
"middot" => 183,
|
34
|
+
"cedil" => 184,
|
35
|
+
"sup1" => 185,
|
36
|
+
"ordm" => 186,
|
37
|
+
"raquo" => 187,
|
38
|
+
"frac14" => 188,
|
39
|
+
"frac12" => 189,
|
40
|
+
"frac34" => 190,
|
41
|
+
"iquest" => 191,
|
42
|
+
"Agrave" => 192,
|
43
|
+
"Aacute" => 193,
|
44
|
+
"Acirc" => 194,
|
45
|
+
"Atilde" => 195,
|
46
|
+
"Auml" => 196,
|
47
|
+
"Aring" => 197,
|
48
|
+
"AElig" => 198,
|
49
|
+
"Ccedil" => 199,
|
50
|
+
"Egrave" => 200,
|
51
|
+
"Eacute" => 201,
|
52
|
+
"Ecirc" => 202,
|
53
|
+
"Euml" => 203,
|
54
|
+
"Igrave" => 204,
|
55
|
+
"Iacute" => 205,
|
56
|
+
"Icirc" => 206,
|
57
|
+
"Iuml" => 207,
|
58
|
+
"ETH" => 208,
|
59
|
+
"Ntilde" => 209,
|
60
|
+
"Ograve" => 210,
|
61
|
+
"Oacute" => 211,
|
62
|
+
"Ocirc" => 212,
|
63
|
+
"Otilde" => 213,
|
64
|
+
"Ouml" => 214,
|
65
|
+
"times" => 215,
|
66
|
+
"Oslash" => 216,
|
67
|
+
"Ugrave" => 217,
|
68
|
+
"Uacute" => 218,
|
69
|
+
"Ucirc" => 219,
|
70
|
+
"Uuml" => 220,
|
71
|
+
"Yacute" => 221,
|
72
|
+
"THORN" => 222,
|
73
|
+
"szlig" => 223,
|
74
|
+
"agrave" => 224,
|
75
|
+
"aacute" => 225,
|
76
|
+
"acirc" => 226,
|
77
|
+
"atilde" => 227,
|
78
|
+
"auml" => 228,
|
79
|
+
"aring" => 229,
|
80
|
+
"aelig" => 230,
|
81
|
+
"ccedil" => 231,
|
82
|
+
"egrave" => 232,
|
83
|
+
"eacute" => 233,
|
84
|
+
"ecirc" => 234,
|
85
|
+
"euml" => 235,
|
86
|
+
"igrave" => 236,
|
87
|
+
"iacute" => 237,
|
88
|
+
"icirc" => 238,
|
89
|
+
"iuml" => 239,
|
90
|
+
"eth" => 240,
|
91
|
+
"ntilde" => 241,
|
92
|
+
"ograve" => 242,
|
93
|
+
"oacute" => 243,
|
94
|
+
"ocirc" => 244,
|
95
|
+
"otilde" => 245,
|
96
|
+
"ouml" => 246,
|
97
|
+
"divide" => 247,
|
98
|
+
"oslash" => 248,
|
99
|
+
"ugrave" => 249,
|
100
|
+
"uacute" => 250,
|
101
|
+
"ucirc" => 251,
|
102
|
+
"uuml" => 252,
|
103
|
+
"yacute" => 253,
|
104
|
+
"thorn" => 254,
|
105
|
+
"yuml" => 255,
|
106
|
+
"OElig" => 338,
|
107
|
+
"oelig" => 339,
|
108
|
+
"Scaron" => 352,
|
109
|
+
"scaron" => 353,
|
110
|
+
"Yuml" => 376,
|
111
|
+
"fnof" => 402,
|
112
|
+
"circ" => 710,
|
113
|
+
"tilde" => 732,
|
114
|
+
"Alpha" => 913,
|
115
|
+
"Beta" => 914,
|
116
|
+
"Gamma" => 915,
|
117
|
+
"Delta" => 916,
|
118
|
+
"Epsilon" => 917,
|
119
|
+
"Zeta" => 918,
|
120
|
+
"Eta" => 919,
|
121
|
+
"Theta" => 920,
|
122
|
+
"Iota" => 921,
|
123
|
+
"Kappa" => 922,
|
124
|
+
"Lambda" => 923,
|
125
|
+
"Mu" => 924,
|
126
|
+
"Nu" => 925,
|
127
|
+
"Xi" => 926,
|
128
|
+
"Omicron" => 927,
|
129
|
+
"Pi" => 928,
|
130
|
+
"Rho" => 929,
|
131
|
+
"Sigma" => 931,
|
132
|
+
"Tau" => 932,
|
133
|
+
"Upsilon" => 933,
|
134
|
+
"Phi" => 934,
|
135
|
+
"Chi" => 935,
|
136
|
+
"Psi" => 936,
|
137
|
+
"Omega" => 937,
|
138
|
+
"alpha" => 945,
|
139
|
+
"beta" => 946,
|
140
|
+
"gamma" => 947,
|
141
|
+
"delta" => 948,
|
142
|
+
"epsilon" => 949,
|
143
|
+
"zeta" => 950,
|
144
|
+
"eta" => 951,
|
145
|
+
"theta" => 952,
|
146
|
+
"iota" => 953,
|
147
|
+
"kappa" => 954,
|
148
|
+
"lambda" => 955,
|
149
|
+
"mu" => 956,
|
150
|
+
"nu" => 957,
|
151
|
+
"xi" => 958,
|
152
|
+
"omicron" => 959,
|
153
|
+
"pi" => 960,
|
154
|
+
"rho" => 961,
|
155
|
+
"sigmaf" => 962,
|
156
|
+
"sigma" => 963,
|
157
|
+
"tau" => 964,
|
158
|
+
"upsilon" => 965,
|
159
|
+
"phi" => 966,
|
160
|
+
"chi" => 967,
|
161
|
+
"psi" => 968,
|
162
|
+
"omega" => 969,
|
163
|
+
"thetasym" => 977,
|
164
|
+
"upsih" => 978,
|
165
|
+
"piv" => 982,
|
166
|
+
"ensp" => 8194,
|
167
|
+
"emsp" => 8195,
|
168
|
+
"thinsp" => 8201,
|
169
|
+
"zwnj" => 8204,
|
170
|
+
"zwj" => 8205,
|
171
|
+
"lrm" => 8206,
|
172
|
+
"rlm" => 8207,
|
173
|
+
"ndash" => 8211,
|
174
|
+
"mdash" => 8212,
|
175
|
+
"lsquo" => 8216,
|
176
|
+
"rsquo" => 8217,
|
177
|
+
"sbquo" => 8218,
|
178
|
+
"ldquo" => 8220,
|
179
|
+
"rdquo" => 8221,
|
180
|
+
"bdquo" => 8222,
|
181
|
+
"dagger" => 8224,
|
182
|
+
"Dagger" => 8225,
|
183
|
+
"bull" => 8226,
|
184
|
+
"hellip" => 8230,
|
185
|
+
"permil" => 8240,
|
186
|
+
"prime" => 8242,
|
187
|
+
"Prime" => 8243,
|
188
|
+
"lsaquo" => 8249,
|
189
|
+
"rsaquo" => 8250,
|
190
|
+
"oline" => 8254,
|
191
|
+
"frasl" => 8260,
|
192
|
+
"euro" => 8364,
|
193
|
+
"image" => 8465,
|
194
|
+
"weierp" => 8472,
|
195
|
+
"real" => 8476,
|
196
|
+
"trade" => 8482,
|
197
|
+
"alefsym" => 8501,
|
198
|
+
"larr" => 8592,
|
199
|
+
"uarr" => 8593,
|
200
|
+
"rarr" => 8594,
|
201
|
+
"darr" => 8595,
|
202
|
+
"harr" => 8596,
|
203
|
+
"crarr" => 8629,
|
204
|
+
"lArr" => 8656,
|
205
|
+
"uArr" => 8657,
|
206
|
+
"rArr" => 8658,
|
207
|
+
"dArr" => 8659,
|
208
|
+
"hArr" => 8660,
|
209
|
+
"forall" => 8704,
|
210
|
+
"part" => 8706,
|
211
|
+
"exist" => 8707,
|
212
|
+
"empty" => 8709,
|
213
|
+
"nabla" => 8711,
|
214
|
+
"isin" => 8712,
|
215
|
+
"notin" => 8713,
|
216
|
+
"ni" => 8715,
|
217
|
+
"prod" => 8719,
|
218
|
+
"sum" => 8721,
|
219
|
+
"minus" => 8722,
|
220
|
+
"lowast" => 8727,
|
221
|
+
"radic" => 8730,
|
222
|
+
"prop" => 8733,
|
223
|
+
"infin" => 8734,
|
224
|
+
"ang" => 8736,
|
225
|
+
"and" => 8743,
|
226
|
+
"or" => 8744,
|
227
|
+
"cap" => 8745,
|
228
|
+
"cup" => 8746,
|
229
|
+
"int" => 8747,
|
230
|
+
"there4" => 8756,
|
231
|
+
"sim" => 8764,
|
232
|
+
"cong" => 8773,
|
233
|
+
"asymp" => 8776,
|
234
|
+
"ne" => 8800,
|
235
|
+
"equiv" => 8801,
|
236
|
+
"le" => 8804,
|
237
|
+
"ge" => 8805,
|
238
|
+
"sub" => 8834,
|
239
|
+
"sup" => 8835,
|
240
|
+
"nsub" => 8836,
|
241
|
+
"sube" => 8838,
|
242
|
+
"supe" => 8839,
|
243
|
+
"oplus" => 8853,
|
244
|
+
"otimes" => 8855,
|
245
|
+
"perp" => 8869,
|
246
|
+
"sdot" => 8901,
|
247
|
+
"lceil" => 8968,
|
248
|
+
"rceil" => 8969,
|
249
|
+
"lfloor" => 8970,
|
250
|
+
"rfloor" => 8971,
|
251
|
+
"lang" => 10216,
|
252
|
+
"rang" => 10217,
|
253
|
+
"loz" => 9674,
|
254
|
+
"spades" => 9824,
|
255
|
+
"clubs" => 9827,
|
256
|
+
"hearts" => 9829,
|
257
|
+
"diams" => 9830
|
258
|
+
}
|
259
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module Sterile
|
4
|
+
SMART_FORMAT_RULES = [
|
5
|
+
["'tain't", "’tain’t"],
|
6
|
+
["'twere", "’twere"],
|
7
|
+
["'twas", "’twas"],
|
8
|
+
["'tis", "’tis"],
|
9
|
+
["'twill", "’twill"],
|
10
|
+
["'til", "’til"],
|
11
|
+
["'bout", "’bout"],
|
12
|
+
["'nuff", "’nuff"],
|
13
|
+
["'round", "’round"],
|
14
|
+
["'cause", "’cause"],
|
15
|
+
["'cos", "’cos"],
|
16
|
+
["i'm", "i’m"],
|
17
|
+
['--"', "—”"],
|
18
|
+
["--'", "—’"],
|
19
|
+
["--", "—"],
|
20
|
+
["...", "…"],
|
21
|
+
["(tm)", "™"],
|
22
|
+
["(TM)", "™"],
|
23
|
+
["(c)", "©"],
|
24
|
+
["(r)", "®"],
|
25
|
+
["(R)", "®"],
|
26
|
+
[/s\'([^a-zA-Z0-9])/, "s’\\1"],
|
27
|
+
[/"([:;])/, "”\\1"],
|
28
|
+
[/\'s$/, "’s"],
|
29
|
+
[/\'(\d\d(?:’|\')?s)/, "’\\1"],
|
30
|
+
[/(\s|\A|"|\(|\[)\'/, "\\1‘"],
|
31
|
+
[/(\d+)"/, "\\1′"],
|
32
|
+
[/(\d+)\'/, "\\1″"],
|
33
|
+
[/(\S)\'([^\'\s])/, "\\1’\\2"],
|
34
|
+
[/(\s|\A|\(|\[)"(?!\s)/, "\\1“\\2"],
|
35
|
+
[/"(\s|\S|\Z)/, "”\\1"],
|
36
|
+
[/\'([\s.]|\Z)/, "’\\1"],
|
37
|
+
[/(\d+)x(\d+)/, "\\1×\\2"],
|
38
|
+
[/([a-z])'(t|d|s|ll|re|ve)(\b)/i, "\\1’\\2\\3"]
|
39
|
+
]
|
40
|
+
end
|
data/lib/sterile.rb
ADDED
@@ -0,0 +1,339 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
# Copyright (c) 2011 Patrick Hogan
|
4
|
+
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
# a copy of this software and associated documentation files (the
|
7
|
+
# "Software"), to deal in the Software without restriction, including
|
8
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
# the following conditions:
|
12
|
+
#
|
13
|
+
# The above copyright notice and this permission notice shall be
|
14
|
+
# included in all copies or substantial portions of the Software.
|
15
|
+
#
|
16
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
23
|
+
|
24
|
+
require "sterile/codepoints"
|
25
|
+
require "sterile/html_entities"
|
26
|
+
require "sterile/smart_format_rules"
|
27
|
+
|
28
|
+
|
29
|
+
module Sterile
|
30
|
+
|
31
|
+
class << self
|
32
|
+
|
33
|
+
def transmogrify(string, &block)
|
34
|
+
raise "No block given" unless block_given?
|
35
|
+
|
36
|
+
result = ""
|
37
|
+
string.unpack("U*").each do |codepoint|
|
38
|
+
cg = codepoint >> 8
|
39
|
+
cp = codepoint & 0xFF
|
40
|
+
begin
|
41
|
+
mapping = CODEPOINTS[cg][cp]
|
42
|
+
result << yield(mapping, codepoint)
|
43
|
+
rescue
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
result
|
48
|
+
end
|
49
|
+
|
50
|
+
# Transliterate Unicode [and accented ASCII] characters to their plain-text
|
51
|
+
# ASCII equivalents. This is based on data from the stringex gem (https://github.com/rsl/stringex)
|
52
|
+
# which is in turn a port of Perl's Unidecode and ostensibly provides
|
53
|
+
# superior results to iconv. The optical conversion data is based on work
|
54
|
+
# by Eric Boehs at https://github.com/ericboehs/to_slug
|
55
|
+
# Passing an option of :optical => true will prefer optical mapping instead
|
56
|
+
# of more pedantic matches.
|
57
|
+
#
|
58
|
+
# "ýůçký".transliterate # => "yucky"
|
59
|
+
#
|
60
|
+
def transliterate(string, options = {})
|
61
|
+
options = {
|
62
|
+
:optical => false
|
63
|
+
}.merge!(options)
|
64
|
+
|
65
|
+
if options[:optical]
|
66
|
+
transmogrify(string) do |mapping, codepoint|
|
67
|
+
mapping[1] || mapping[0] || ""
|
68
|
+
end
|
69
|
+
else
|
70
|
+
transmogrify(string) do |mapping, codepoint|
|
71
|
+
mapping[0] || mapping[1] || ""
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
alias_method :to_ascii, :transliterate
|
76
|
+
|
77
|
+
|
78
|
+
# Trim whitespace from start and end of string and remove any redundant
|
79
|
+
# whitespace in between.
|
80
|
+
#
|
81
|
+
# " Hello world! ".transliterate # => "Hello world!"
|
82
|
+
#
|
83
|
+
def trim_whitespace(string)
|
84
|
+
string.gsub(/\s+/, " ").strip
|
85
|
+
end
|
86
|
+
|
87
|
+
|
88
|
+
# Transliterate to ASCII and strip out any HTML/XML tags.
|
89
|
+
#
|
90
|
+
# "<b>nåsty</b>".sterilize # => "nasty"
|
91
|
+
#
|
92
|
+
def sterilize(string)
|
93
|
+
strip_tags(transliterate(string))
|
94
|
+
end
|
95
|
+
|
96
|
+
|
97
|
+
# Transliterate to ASCII, downcase and format for URL permalink/slug
|
98
|
+
# by stripping out all non-alphanumeric characters and replacing spaces
|
99
|
+
# with a delimiter (defaults to '-').
|
100
|
+
#
|
101
|
+
# "Hello World!".sluggerize # => "hello-world"
|
102
|
+
#
|
103
|
+
def sluggerize(string, options = {})
|
104
|
+
options = {
|
105
|
+
:delimiter => "-"
|
106
|
+
}.merge!(options)
|
107
|
+
|
108
|
+
sterilize(string).strip.gsub(/\s+/, "-").gsub(/[^a-zA-Z0-9\-]/, "").gsub(/-+/, options[:delimiter]).downcase
|
109
|
+
end
|
110
|
+
alias_method :to_slug, :sluggerize
|
111
|
+
|
112
|
+
|
113
|
+
# Format text with proper "curly" quotes, m-dashes, copyright, trademark, etc.
|
114
|
+
#
|
115
|
+
# q{"He said, 'Away with you, Drake!'"}.smart_format # => “He said, ‘Away with you, Drake!’”
|
116
|
+
#
|
117
|
+
def smart_format(string)
|
118
|
+
SMART_FORMAT_RULES.each do |rule|
|
119
|
+
string.gsub!(rule[0], rule[1])
|
120
|
+
end
|
121
|
+
string
|
122
|
+
end
|
123
|
+
|
124
|
+
|
125
|
+
# Turn Unicode characters into their HTML equivilents.
|
126
|
+
# If a valid HTML entity is not possible, it will create a numeric entity.
|
127
|
+
#
|
128
|
+
# q{“Economy Hits Bottom,” ran the headline}.smart_format # => “Economy Hits Bottom,” ran the headline
|
129
|
+
#
|
130
|
+
def encode_entities(string)
|
131
|
+
transmogrify(string) do |mapping, codepoint|
|
132
|
+
if (32..126).include?(codepoint)
|
133
|
+
mapping[0]
|
134
|
+
else
|
135
|
+
"&" + (mapping[2] || "#" + codepoint.to_s) + ";"
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
|
141
|
+
# The reverse of +encode_entities+. Turns HTML or numeric entities into
|
142
|
+
# their Unicode counterparts.
|
143
|
+
#
|
144
|
+
def decode_entities(string)
|
145
|
+
string.gsub!(/&#(\d{1,4});/) { [$1.to_i].pack("U") }
|
146
|
+
string.gsub(/&([a-zA-Z0-9]+);/) do
|
147
|
+
codepoint = HTML_ENTITIES[$1]
|
148
|
+
codepoint ? [codepoint].pack("U") : $&
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
|
153
|
+
# Remove HTML/XML tags from text. Also strips out comments, PHP and ERB style tags.
|
154
|
+
# CDATA is considered text unless :keep_cdata => false is specified.
|
155
|
+
# Redundant whitespace will be removed unless :keep_whitespace => true is specified.
|
156
|
+
#
|
157
|
+
def strip_tags(string, options = {})
|
158
|
+
options = {
|
159
|
+
:keep_whitespace => false,
|
160
|
+
:keep_cdata => true
|
161
|
+
}.merge!(options)
|
162
|
+
|
163
|
+
string.gsub!(/<[%?](php)?[^>]*>/, '') # strip php, erb et al
|
164
|
+
string.gsub!(/<!--[^-]*-->/, '') # strip comments
|
165
|
+
|
166
|
+
string.gsub!(
|
167
|
+
/
|
168
|
+
<!\[CDATA\[
|
169
|
+
([^\]]*)
|
170
|
+
\]\]>
|
171
|
+
/xi,
|
172
|
+
options[:keep_cdata] ? '\\1' : ''
|
173
|
+
)
|
174
|
+
|
175
|
+
html_name = /[\w:-]+/
|
176
|
+
html_data = /([A-Za-z0-9]+|('[^']*?'|"[^"]*?"))/
|
177
|
+
html_attr = /(#{html_name}(\s*=\s*#{html_data})?)/
|
178
|
+
|
179
|
+
string.gsub!(
|
180
|
+
/
|
181
|
+
<
|
182
|
+
[\/]?
|
183
|
+
#{html_name}
|
184
|
+
(\s+(#{html_attr}(\s+#{html_attr})*))?
|
185
|
+
\s*
|
186
|
+
[\/]?
|
187
|
+
>
|
188
|
+
/xi,
|
189
|
+
''
|
190
|
+
)
|
191
|
+
|
192
|
+
options[:keep_whitespace] ? string : trim_whitespace(string)
|
193
|
+
end
|
194
|
+
|
195
|
+
|
196
|
+
# Similar to +gsub+, except it works in between HTML/XML tags and
|
197
|
+
# yields text to a block. Text will be replaced by what the block
|
198
|
+
# returns.
|
199
|
+
# Warning: does not work in some degenerate cases.
|
200
|
+
#
|
201
|
+
def gsub_tags(string, &block)
|
202
|
+
raise "No block given" unless block_given?
|
203
|
+
|
204
|
+
string.gsub!(/(<[^>]*>)|([^<]+)/) do |match|
|
205
|
+
$2 ? yield($2) : $1
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
|
210
|
+
# Iterates over all text in between HTML/XML tags and yields
|
211
|
+
# it to a block.
|
212
|
+
# Warning: does not work in some degenerate cases.
|
213
|
+
#
|
214
|
+
def scan_tags(string, &block)
|
215
|
+
raise "No block given" unless block_given?
|
216
|
+
|
217
|
+
string.scan(/(<[^>]*>)|([^<]+)/) do |match|
|
218
|
+
yield($2) unless $2.nil?
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
|
223
|
+
# Like +smart_format+, but works with HTML/XML (somewhat).
|
224
|
+
#
|
225
|
+
def smart_format_tags(string)
|
226
|
+
string.gsub_tags do |text|
|
227
|
+
text.smart_format.encode_entities
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
|
232
|
+
# Format text appropriately for titles. This method is much smarter
|
233
|
+
# than ActiveSupport's +titlecase+. The algorithm is based on work done
|
234
|
+
# by John Gruber et al (http://daringfireball.net/2008/08/title_case_update)
|
235
|
+
#
|
236
|
+
def titlecase(string)
|
237
|
+
string.strip!
|
238
|
+
string.gsub!(/\s+/, " ")
|
239
|
+
string.downcase! unless string =~ /[[:lower:]]/
|
240
|
+
|
241
|
+
small_words = %w{ a an and as at(?!&t) but by en for if in nor of on or the to v[.]? via vs[.]? }.join("|")
|
242
|
+
apos = / (?: ['’] [[:lower:]]* )? /xu
|
243
|
+
|
244
|
+
string.gsub!(
|
245
|
+
/
|
246
|
+
\b
|
247
|
+
([_\*]*)
|
248
|
+
(?:
|
249
|
+
( [-\+\w]+ [@.\:\/] [-\w@.\:\/]+ #{apos} ) # URL, domain, or email
|
250
|
+
|
|
251
|
+
( (?i: #{small_words} ) #{apos} ) # or small word, case-insensitive
|
252
|
+
|
|
253
|
+
( [[:alpha:]] [[:lower:]'’()\[\]{}]* #{apos} ) # or word without internal caps
|
254
|
+
|
|
255
|
+
( [[:alpha:]] [[:alpha:]'’()\[\]{}]* #{apos} ) # or some other word
|
256
|
+
)
|
257
|
+
([_\*]*)
|
258
|
+
\b
|
259
|
+
/xu
|
260
|
+
) do
|
261
|
+
($1 ? $1 : "") +
|
262
|
+
($2 ? $2 : ($3 ? $3.downcase : ($4 ? $4.downcase.capitalize : $5))) +
|
263
|
+
($6 ? $6 : "")
|
264
|
+
end
|
265
|
+
|
266
|
+
if RUBY_VERSION < "1.9.0"
|
267
|
+
string.gsub!(
|
268
|
+
/
|
269
|
+
\b
|
270
|
+
([:alpha:]+)
|
271
|
+
(‑)
|
272
|
+
([:alpha:]+)
|
273
|
+
\b
|
274
|
+
/xu
|
275
|
+
) do
|
276
|
+
$1.downcase.capitalize + $2 + $1.downcase.capitalize
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
280
|
+
string.gsub!(
|
281
|
+
/
|
282
|
+
(
|
283
|
+
\A [[:punct:]]* # start of title
|
284
|
+
| [:.;?!][ ]+ # or of subsentence
|
285
|
+
| [ ]['"“‘(\[][ ]* # or of inserted subphrase
|
286
|
+
)
|
287
|
+
( #{small_words} ) # followed by a small-word
|
288
|
+
\b
|
289
|
+
/xiu
|
290
|
+
) do
|
291
|
+
$1 + $2.downcase.capitalize
|
292
|
+
end
|
293
|
+
|
294
|
+
string.gsub!(
|
295
|
+
/
|
296
|
+
\b
|
297
|
+
( #{small_words} ) # small-word
|
298
|
+
(?=
|
299
|
+
[[:punct:]]* \Z # at the end of the title
|
300
|
+
|
|
301
|
+
['"’”)\]] [ ] # or of an inserted subphrase
|
302
|
+
)
|
303
|
+
/xu
|
304
|
+
) do
|
305
|
+
$1.downcase.capitalize
|
306
|
+
end
|
307
|
+
|
308
|
+
string.gsub!(
|
309
|
+
/
|
310
|
+
(
|
311
|
+
\b
|
312
|
+
[[:alpha:]] # single first letter
|
313
|
+
[\-‑] # followed by a dash
|
314
|
+
)
|
315
|
+
( [[:alpha:]] ) # followed by a letter
|
316
|
+
/xu
|
317
|
+
) do
|
318
|
+
$1 + $2.downcase
|
319
|
+
end
|
320
|
+
|
321
|
+
string.gsub!(/q&a/i, 'Q&A')
|
322
|
+
|
323
|
+
string
|
324
|
+
end
|
325
|
+
|
326
|
+
end
|
327
|
+
|
328
|
+
end
|
329
|
+
|
330
|
+
|
331
|
+
# Add extensions to String
|
332
|
+
#
|
333
|
+
class String
|
334
|
+
Sterile.methods(false).each do |method|
|
335
|
+
eval("def #{method}(*args, &block); Sterile.#{method}(self, *args, &block); end")
|
336
|
+
eval("def #{method}!(*args, &block); replace Sterile.#{method}(self, *args, &block); end")
|
337
|
+
end
|
338
|
+
end
|
339
|
+
|
data/sterilize.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
$:.push File.expand_path("../lib", __FILE__)
|
4
|
+
require "sterile/version"
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = "sterile"
|
8
|
+
s.version = Sterile::VERSION
|
9
|
+
s.platform = Gem::Platform::RUBY
|
10
|
+
s.authors = ["Patrick Hogan"]
|
11
|
+
s.email = ["pbhogan@gmail.com"]
|
12
|
+
s.homepage = "https://github.com/pbhogan/sterile"
|
13
|
+
s.summary = %q{Sterilize your strings! Transliterate, generate slugs, smart format, strip tags, encode/decode entities and more.}
|
14
|
+
s.description = s.summary
|
15
|
+
|
16
|
+
s.rubyforge_project = "sterile"
|
17
|
+
|
18
|
+
# s.add_dependency("nokogiri")
|
19
|
+
|
20
|
+
s.files = `git ls-files`.split("\n")
|
21
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
22
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
23
|
+
s.require_paths = ["lib"]
|
24
|
+
end
|