name_tamer 0.6.0 → 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.
- checksums.yaml +5 -5
- data/CHANGELOG.md +41 -0
- data/LICENSE +1 -1
- data/README.md +60 -34
- data/lib/name-tamer.rb +2 -0
- data/lib/name_tamer/constants/adfixes_prefix_person +44 -0
- data/lib/name_tamer/constants/adfixes_suffix_organization +213 -0
- data/lib/name_tamer/constants/adfixes_suffix_person +131 -0
- data/lib/name_tamer/constants.rb +28 -60
- data/lib/name_tamer/name/private_methods_nice_name.rb +116 -0
- data/lib/name_tamer/name/private_methods_simple_name.rb +71 -0
- data/lib/name_tamer/name/utilities.rb +84 -0
- data/lib/name_tamer/name.rb +13 -294
- data/lib/name_tamer/strings/approximations.rb +209 -0
- data/lib/name_tamer/strings/bad_encoding.rb +148 -0
- data/lib/name_tamer/strings/capitalization.rb +46 -0
- data/lib/name_tamer/strings/compound_names.rb +34 -0
- data/lib/name_tamer/strings/core.rb +62 -0
- data/lib/name_tamer/strings/name_modifiers.rb +51 -0
- data/lib/name_tamer/strings/spacing.rb +22 -0
- data/lib/name_tamer/strings.rb +9 -0
- data/lib/name_tamer/text.rb +21 -13
- data/lib/name_tamer/version.rb +3 -1
- data/lib/name_tamer.rb +2 -3
- metadata +26 -36
- data/.codeclimate.yml +0 -18
- data/.env +0 -1
- data/.gitignore +0 -26
- data/.hound.yml +0 -6
- data/.rspec +0 -2
- data/.rubocop.yml +0 -63
- data/.travis.yml +0 -13
- data/Gemfile +0 -20
- data/Guardfile +0 -16
- data/Rakefile +0 -14
- data/doc/maintenance.rake +0 -76
- data/doc/prefixes.csv +0 -49
- data/doc/suffixes.csv +0 -345
- data/lib/name_tamer/array.rb +0 -8
- data/lib/name_tamer/string.rb +0 -280
- data/name_tamer.gemspec +0 -19
- data/spec/name_tamer/name_spec.rb +0 -95
- data/spec/name_tamer/string_spec.rb +0 -5
- data/spec/name_tamer/text_spec.rb +0 -40
- data/spec/spec_helper.rb +0 -14
- data/spec/support/names.yml +0 -741
data/lib/name_tamer/name.rb
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'name/private_methods_nice_name'
|
|
4
|
+
require_relative 'name/private_methods_simple_name'
|
|
5
|
+
require_relative 'name/utilities'
|
|
6
|
+
|
|
1
7
|
module NameTamer
|
|
2
8
|
class Name
|
|
3
9
|
# References:
|
|
@@ -50,7 +56,7 @@ module NameTamer
|
|
|
50
56
|
remove_periods_from_initials # "J.P.R. Williams" -> "JPR Williams"
|
|
51
57
|
standardize_words # "B&Q Intl" -> "B and Q International"
|
|
52
58
|
|
|
53
|
-
@simple_name.whitespace_to
|
|
59
|
+
@simple_name = Strings.whitespace_to(@simple_name, ASCII_SPACE)
|
|
54
60
|
end
|
|
55
61
|
|
|
56
62
|
@simple_name
|
|
@@ -70,228 +76,20 @@ module NameTamer
|
|
|
70
76
|
end
|
|
71
77
|
|
|
72
78
|
def contact_type=(new_contact_type)
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
unless @contact_type.nil? || @contact_type == ct_as_sym
|
|
76
|
-
puts "Changing contact type of #{@name} from #{@contact_type} to #{new_contact_type}"
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
@contact_type = ct_as_sym
|
|
79
|
+
@contact_type = new_contact_type.to_sym
|
|
80
80
|
end
|
|
81
81
|
|
|
82
82
|
# Useful method for iterating through the words in the name
|
|
83
|
-
def each_word(&
|
|
83
|
+
def each_word(&)
|
|
84
84
|
@words ||= slug.split(SLUG_DELIMITER)
|
|
85
|
-
@words.each(&
|
|
85
|
+
@words.each(&)
|
|
86
86
|
end
|
|
87
87
|
|
|
88
|
-
# These lines aren't used and aren't covered by specs
|
|
89
|
-
# def name=(new_name)
|
|
90
|
-
# initialize new_name, :contact_type => @contact_type
|
|
91
|
-
# end
|
|
92
|
-
#
|
|
93
|
-
# def to_hash
|
|
94
|
-
# {
|
|
95
|
-
# name: name,
|
|
96
|
-
# nice_name: nice_name,
|
|
97
|
-
# simple_name: simple_name,
|
|
98
|
-
# slug: slug,
|
|
99
|
-
# contact_type: contact_type,
|
|
100
|
-
# last_name: last_name,
|
|
101
|
-
# remainder: remainder,
|
|
102
|
-
# adfix_found: adfix_found
|
|
103
|
-
# }
|
|
104
|
-
# end
|
|
105
|
-
|
|
106
88
|
private
|
|
107
89
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
def unescape
|
|
113
|
-
@tidy_name.ensure_safe!.safe_unescape!.unescape_html!
|
|
114
|
-
end
|
|
115
|
-
|
|
116
|
-
def remove_zero_width
|
|
117
|
-
@tidy_name.strip_unwanted!(ZERO_WIDTH_FILTER)
|
|
118
|
-
end
|
|
119
|
-
|
|
120
|
-
def tidy_spacing
|
|
121
|
-
@tidy_name
|
|
122
|
-
.space_around_comma!
|
|
123
|
-
.strip_or_self!
|
|
124
|
-
.whitespace_to!(ASCII_SPACE)
|
|
125
|
-
end
|
|
126
|
-
|
|
127
|
-
def fix_encoding_errors
|
|
128
|
-
@tidy_name.fix_encoding_errors!
|
|
129
|
-
end
|
|
130
|
-
|
|
131
|
-
# Remove spaces from groups of initials
|
|
132
|
-
def consolidate_initials
|
|
133
|
-
@tidy_name
|
|
134
|
-
.remove_spaces_from_initials!
|
|
135
|
-
.ensure_space_after_initials!
|
|
136
|
-
end
|
|
137
|
-
|
|
138
|
-
# An adfix is either a prefix or a suffix
|
|
139
|
-
def remove_adfixes
|
|
140
|
-
if @last_name.nil?
|
|
141
|
-
# Our name is still in one part, not two
|
|
142
|
-
loop do
|
|
143
|
-
@nice_name = remove_outermost_adfix(:suffix, @nice_name)
|
|
144
|
-
break unless @adfix_found
|
|
145
|
-
end
|
|
146
|
-
|
|
147
|
-
loop do
|
|
148
|
-
@nice_name = remove_outermost_adfix(:prefix, @nice_name)
|
|
149
|
-
break unless @adfix_found
|
|
150
|
-
end
|
|
151
|
-
else
|
|
152
|
-
# Our name is currently in two halves
|
|
153
|
-
loop do
|
|
154
|
-
@last_name = remove_outermost_adfix(:suffix, @last_name)
|
|
155
|
-
break unless @adfix_found
|
|
156
|
-
end
|
|
157
|
-
|
|
158
|
-
loop do
|
|
159
|
-
@remainder = remove_outermost_adfix(:prefix, @remainder)
|
|
160
|
-
break unless @adfix_found
|
|
161
|
-
end
|
|
162
|
-
end
|
|
163
|
-
end
|
|
164
|
-
|
|
165
|
-
# Names in the form "Smith, John" need to be turned around to "John Smith"
|
|
166
|
-
def fixup_last_name_first
|
|
167
|
-
return if @contact_type == :organization
|
|
168
|
-
|
|
169
|
-
parts = @nice_name.split ', '
|
|
170
|
-
|
|
171
|
-
return unless parts.count == 2
|
|
172
|
-
|
|
173
|
-
@last_name = parts[0] # Sometimes the last name alone is all caps and we can name-case it
|
|
174
|
-
@remainder = parts[1]
|
|
175
|
-
end
|
|
176
|
-
|
|
177
|
-
# Sometimes we end up with mismatched braces after adfix stripping
|
|
178
|
-
# e.g. "Ceres (Ceres Holdings LLC)" -> "Ceres (Ceres Holdings"
|
|
179
|
-
def fixup_mismatched_braces
|
|
180
|
-
left_brace_count = @nice_name.count '('
|
|
181
|
-
right_brace_count = @nice_name.count ')'
|
|
182
|
-
|
|
183
|
-
if left_brace_count > right_brace_count
|
|
184
|
-
@nice_name += ')'
|
|
185
|
-
elsif left_brace_count < right_brace_count
|
|
186
|
-
@nice_name = '(' + @nice_name
|
|
187
|
-
end
|
|
188
|
-
end
|
|
189
|
-
|
|
190
|
-
def name_wrangle
|
|
191
|
-
# Fix case if all caps or all lowercase
|
|
192
|
-
if @last_name.nil?
|
|
193
|
-
name_wrangle_single_name
|
|
194
|
-
else
|
|
195
|
-
name_wrangle_split_name
|
|
196
|
-
end
|
|
197
|
-
end
|
|
198
|
-
|
|
199
|
-
def name_wrangle_single_name
|
|
200
|
-
lowercase = @nice_name.downcase
|
|
201
|
-
uppercase = @nice_name.upcase
|
|
202
|
-
fix_case = false
|
|
203
|
-
|
|
204
|
-
if @contact_type == :organization
|
|
205
|
-
fix_case = true if @nice_name == uppercase && @nice_name.length > 4
|
|
206
|
-
elsif [uppercase, lowercase].include?(@nice_name)
|
|
207
|
-
fix_case = true
|
|
208
|
-
end
|
|
209
|
-
|
|
210
|
-
@nice_name = name_case(lowercase) if fix_case
|
|
211
|
-
end
|
|
212
|
-
|
|
213
|
-
def name_wrangle_split_name
|
|
214
|
-
# It's a person if we've split the name, so no organization logic here
|
|
215
|
-
lowercase = @last_name.downcase
|
|
216
|
-
uppercase = @last_name.upcase
|
|
217
|
-
@last_name = name_case(lowercase) if [uppercase, lowercase].include?(@last_name)
|
|
218
|
-
@nice_name = "#{@remainder} #{@last_name}"
|
|
219
|
-
end
|
|
220
|
-
|
|
221
|
-
# Conjoin compound names with non-breaking spaces
|
|
222
|
-
def use_nonbreaking_spaces_in_compound_names
|
|
223
|
-
@nice_name
|
|
224
|
-
.nbsp_in_compound_name!
|
|
225
|
-
.nbsp_in_name_modifier!
|
|
226
|
-
end
|
|
227
|
-
|
|
228
|
-
#--------------------------------------------------------
|
|
229
|
-
# Make search name from nice name
|
|
230
|
-
#--------------------------------------------------------
|
|
231
|
-
|
|
232
|
-
# Remove initials from personal names unless they are the only identifier.
|
|
233
|
-
# i.e. only remove initials if there's also a proper name there
|
|
234
|
-
def remove_initials
|
|
235
|
-
return unless @contact_type == :person
|
|
236
|
-
|
|
237
|
-
temp_name = @simple_name.gsub(/\b([a-z](?:\.*\s+|\.))/i, '')
|
|
238
|
-
|
|
239
|
-
# If the name still has at least one space we're OK
|
|
240
|
-
@simple_name = temp_name if temp_name.include?(ASCII_SPACE)
|
|
241
|
-
end
|
|
242
|
-
|
|
243
|
-
def remove_middle_names
|
|
244
|
-
return unless @contact_type == :person
|
|
245
|
-
|
|
246
|
-
first_name, parts = find_first_usable_name(@simple_name.split)
|
|
247
|
-
last_name, = find_last_usable_name(parts)
|
|
248
|
-
|
|
249
|
-
return unless first_name || last_name
|
|
250
|
-
|
|
251
|
-
separator = first_name && last_name ? ' ' : ''
|
|
252
|
-
@simple_name = "#{first_name}#{separator}#{last_name}"
|
|
253
|
-
end
|
|
254
|
-
|
|
255
|
-
def find_first_usable_name(parts)
|
|
256
|
-
part = nil
|
|
257
|
-
|
|
258
|
-
parts.each_index do |i|
|
|
259
|
-
part = parts[i]
|
|
260
|
-
next if part.gsub(FILTER_COMPAT, '').empty?
|
|
261
|
-
parts = parts.slice(i + 1, parts.length) # don't use "slice!"
|
|
262
|
-
break
|
|
263
|
-
end
|
|
264
|
-
|
|
265
|
-
[part, parts]
|
|
266
|
-
end
|
|
267
|
-
|
|
268
|
-
def find_last_usable_name(parts)
|
|
269
|
-
part = nil
|
|
270
|
-
|
|
271
|
-
parts.reverse_each do |p|
|
|
272
|
-
next if p.gsub(FILTER_COMPAT, '').empty?
|
|
273
|
-
part = p
|
|
274
|
-
break
|
|
275
|
-
end
|
|
276
|
-
|
|
277
|
-
part
|
|
278
|
-
end
|
|
279
|
-
|
|
280
|
-
def remove_periods_from_initials
|
|
281
|
-
@simple_name.remove_periods_from_initials!
|
|
282
|
-
end
|
|
283
|
-
|
|
284
|
-
def standardize_words
|
|
285
|
-
@simple_name.gsub!(/ *& */, ' and ') # replace ampersand characters with ' and '
|
|
286
|
-
@simple_name.gsub!(/ *\+ */, ' plus ') # replace plus signs with ' plus '
|
|
287
|
-
@simple_name.gsub!(/\bintl\b/i, 'International') # replace 'intl' with 'International'
|
|
288
|
-
@simple_name.gsub!(/[־‐‑‒–—―−﹘﹣-]/, SLUG_DELIMITER) # Replace Unicode dashes with ASCII hyphen
|
|
289
|
-
@simple_name.strip_unwanted!(/["“”™℠®©℗]/) # remove quotes and commercial decoration
|
|
290
|
-
end
|
|
291
|
-
|
|
292
|
-
#--------------------------------------------------------
|
|
293
|
-
# Initialization and utilities
|
|
294
|
-
#--------------------------------------------------------
|
|
90
|
+
include Name::PrivateMethodsNiceName
|
|
91
|
+
include Name::PrivateMethodsSimpleName
|
|
92
|
+
include Name::Utilities
|
|
295
93
|
|
|
296
94
|
def initialize(new_name, args = {})
|
|
297
95
|
@name = new_name || ''
|
|
@@ -307,84 +105,5 @@ module NameTamer
|
|
|
307
105
|
|
|
308
106
|
@adfix_found = false
|
|
309
107
|
end
|
|
310
|
-
|
|
311
|
-
def contact_type_from(args)
|
|
312
|
-
args_ct = args[:contact_type]
|
|
313
|
-
return unless args_ct
|
|
314
|
-
|
|
315
|
-
ct = args_ct.is_a?(Symbol) ? args_ct : args_ct.dup
|
|
316
|
-
ct = ct.to_s unless [String, Symbol].include? ct.class
|
|
317
|
-
ct.downcase! if ct.class == String
|
|
318
|
-
ct = ct.to_sym
|
|
319
|
-
ct = nil unless %i[person organization].include? ct
|
|
320
|
-
|
|
321
|
-
ct
|
|
322
|
-
end
|
|
323
|
-
|
|
324
|
-
# If we don't know the contact type, what's our best guess?
|
|
325
|
-
def contact_type_best_effort
|
|
326
|
-
if @contact_type
|
|
327
|
-
@contact_type
|
|
328
|
-
else
|
|
329
|
-
# If it's just one word we'll assume organization.
|
|
330
|
-
# If more then we'll assume a person
|
|
331
|
-
@name.include?(ASCII_SPACE) ? :person : :organization
|
|
332
|
-
end
|
|
333
|
-
end
|
|
334
|
-
|
|
335
|
-
# We pass to this routine either prefixes or suffixes
|
|
336
|
-
def remove_outermost_adfix(adfix_type, name_part)
|
|
337
|
-
ct, parts = find_contact_type_and_parts(ADFIX_PATTERNS[adfix_type], name_part)
|
|
338
|
-
|
|
339
|
-
return name_part unless @adfix_found
|
|
340
|
-
|
|
341
|
-
# If we've found a diagnostic adfix then set the contact type
|
|
342
|
-
self.contact_type = ct
|
|
343
|
-
|
|
344
|
-
# The remainder of the name will be in parts[0] or parts[2] depending
|
|
345
|
-
# on whether this is a prefix or a suffix.
|
|
346
|
-
# We'll also remove any trailing commas we've exposed.
|
|
347
|
-
(parts[0] + parts[2]).gsub(/\s*,\s*$/, '')
|
|
348
|
-
end
|
|
349
|
-
|
|
350
|
-
def find_contact_type_and_parts(adfixes, name_part)
|
|
351
|
-
ct = contact_type_best_effort
|
|
352
|
-
parts = name_part.partition adfixes[ct]
|
|
353
|
-
@adfix_found = !parts[1].empty?
|
|
354
|
-
|
|
355
|
-
return [ct, parts] if @contact_type || @adfix_found
|
|
356
|
-
|
|
357
|
-
# If the contact type is indeterminate and we didn't find a diagnostic adfix
|
|
358
|
-
# for a person then try again for an organization
|
|
359
|
-
ct = :organization
|
|
360
|
-
parts = name_part.partition adfixes[ct]
|
|
361
|
-
@adfix_found = !parts[1].empty?
|
|
362
|
-
|
|
363
|
-
[ct, parts]
|
|
364
|
-
end
|
|
365
|
-
|
|
366
|
-
# Original Version of NameCase:
|
|
367
|
-
# Copyright (c) Mark Summerfield 1998-2008. All Rights Reserved
|
|
368
|
-
# This module may be used/distributed/modified under the same terms as Perl itself
|
|
369
|
-
# http://dev.perl.org/licenses/ (GPL)
|
|
370
|
-
#
|
|
371
|
-
# Ruby Version:
|
|
372
|
-
# Copyright (c) Aaron Patterson 2006
|
|
373
|
-
# NameCase is distributed under the GPL license.
|
|
374
|
-
#
|
|
375
|
-
# Substantially modified for Xendata
|
|
376
|
-
# Improved in several areas, also now adds non-breaking spaces for
|
|
377
|
-
# compound names like "van der Pump"
|
|
378
|
-
def name_case(lowercase)
|
|
379
|
-
n = lowercase.dup # We assume the name is passed already downcased
|
|
380
|
-
|
|
381
|
-
n
|
|
382
|
-
.upcase_first_letter!
|
|
383
|
-
.downcase_after_apostrophe!
|
|
384
|
-
.fix_mac!
|
|
385
|
-
.fix_ff!
|
|
386
|
-
.fix_name_modifiers!
|
|
387
|
-
.upcase_initials!
|
|
388
|
-
end
|
|
389
108
|
end
|
|
390
109
|
end
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Transliterations (like the i18n defaults)
|
|
4
|
+
# see https://github.com/svenfuchs/i18n/blob/master/lib/i18n/backend/transliterator.rb
|
|
5
|
+
module NameTamer
|
|
6
|
+
module Strings
|
|
7
|
+
module_function
|
|
8
|
+
|
|
9
|
+
# Any characters that resemble latin characters might usefully be
|
|
10
|
+
# transliterated into ones that are easy to type on an anglophone
|
|
11
|
+
# keyboard.
|
|
12
|
+
def approximate_latin_chars(string)
|
|
13
|
+
string.gsub(/[^\x00-\x7f]/u) { |char| APPROXIMATIONS[char] || char }
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
APPROXIMATIONS = {
|
|
17
|
+
'İ' => 'I',
|
|
18
|
+
'×' => 'x',
|
|
19
|
+
'ß' => 'ss',
|
|
20
|
+
'À' => 'A',
|
|
21
|
+
'à' => 'a',
|
|
22
|
+
'Á' => 'A',
|
|
23
|
+
'á' => 'a',
|
|
24
|
+
'Â' => 'A',
|
|
25
|
+
'â' => 'a',
|
|
26
|
+
'Ã' => 'A',
|
|
27
|
+
'ã' => 'a',
|
|
28
|
+
'Ä' => 'A',
|
|
29
|
+
'ä' => 'a',
|
|
30
|
+
'Å' => 'A',
|
|
31
|
+
'å' => 'a',
|
|
32
|
+
'Æ' => 'AE',
|
|
33
|
+
'æ' => 'ae',
|
|
34
|
+
'Ç' => 'C',
|
|
35
|
+
'ç' => 'c',
|
|
36
|
+
'È' => 'E',
|
|
37
|
+
'è' => 'e',
|
|
38
|
+
'É' => 'E',
|
|
39
|
+
'é' => 'e',
|
|
40
|
+
'Ê' => 'E',
|
|
41
|
+
'ê' => 'e',
|
|
42
|
+
'Ë' => 'E',
|
|
43
|
+
'ë' => 'e',
|
|
44
|
+
'Ì' => 'I',
|
|
45
|
+
'ì' => 'i',
|
|
46
|
+
'Í' => 'I',
|
|
47
|
+
'í' => 'i',
|
|
48
|
+
'Î' => 'I',
|
|
49
|
+
'î' => 'i',
|
|
50
|
+
'Ï' => 'I',
|
|
51
|
+
'ï' => 'i',
|
|
52
|
+
'Ð' => 'D',
|
|
53
|
+
'ð' => 'd',
|
|
54
|
+
'Ñ' => 'N',
|
|
55
|
+
'ñ' => 'n',
|
|
56
|
+
'Ò' => 'O',
|
|
57
|
+
'ò' => 'o',
|
|
58
|
+
'Ó' => 'O',
|
|
59
|
+
'ó' => 'o',
|
|
60
|
+
'Ô' => 'O',
|
|
61
|
+
'ô' => 'o',
|
|
62
|
+
'Õ' => 'O',
|
|
63
|
+
'õ' => 'o',
|
|
64
|
+
'Ö' => 'O',
|
|
65
|
+
'ö' => 'o',
|
|
66
|
+
'Ø' => 'O',
|
|
67
|
+
'ø' => 'o',
|
|
68
|
+
'Ù' => 'U',
|
|
69
|
+
'ù' => 'u',
|
|
70
|
+
'Ú' => 'U',
|
|
71
|
+
'ú' => 'u',
|
|
72
|
+
'Û' => 'U',
|
|
73
|
+
'û' => 'u',
|
|
74
|
+
'Ü' => 'U',
|
|
75
|
+
'ü' => 'u',
|
|
76
|
+
'Ý' => 'Y',
|
|
77
|
+
'ý' => 'y',
|
|
78
|
+
'Þ' => 'Th',
|
|
79
|
+
'þ' => 'th',
|
|
80
|
+
'ÿ' => 'y',
|
|
81
|
+
'Ÿ' => 'Y',
|
|
82
|
+
'Ā' => 'A',
|
|
83
|
+
'ā' => 'a',
|
|
84
|
+
'Ă' => 'A',
|
|
85
|
+
'ă' => 'a',
|
|
86
|
+
'Ą' => 'A',
|
|
87
|
+
'ą' => 'a',
|
|
88
|
+
'Ć' => 'C',
|
|
89
|
+
'ć' => 'c',
|
|
90
|
+
'Ĉ' => 'C',
|
|
91
|
+
'ĉ' => 'c',
|
|
92
|
+
'Ċ' => 'C',
|
|
93
|
+
'ċ' => 'c',
|
|
94
|
+
'Č' => 'C',
|
|
95
|
+
'č' => 'c',
|
|
96
|
+
'Ď' => 'D',
|
|
97
|
+
'ď' => 'd',
|
|
98
|
+
'Đ' => 'D',
|
|
99
|
+
'đ' => 'd',
|
|
100
|
+
'Ē' => 'E',
|
|
101
|
+
'ē' => 'e',
|
|
102
|
+
'Ĕ' => 'E',
|
|
103
|
+
'ĕ' => 'e',
|
|
104
|
+
'Ė' => 'E',
|
|
105
|
+
'ė' => 'e',
|
|
106
|
+
'Ę' => 'E',
|
|
107
|
+
'ę' => 'e',
|
|
108
|
+
'Ě' => 'E',
|
|
109
|
+
'ě' => 'e',
|
|
110
|
+
'Ĝ' => 'G',
|
|
111
|
+
'ĝ' => 'g',
|
|
112
|
+
'Ğ' => 'G',
|
|
113
|
+
'ğ' => 'g',
|
|
114
|
+
'Ġ' => 'G',
|
|
115
|
+
'ġ' => 'g',
|
|
116
|
+
'Ģ' => 'G',
|
|
117
|
+
'ģ' => 'g',
|
|
118
|
+
'Ĥ' => 'H',
|
|
119
|
+
'ĥ' => 'h',
|
|
120
|
+
'Ħ' => 'H',
|
|
121
|
+
'ħ' => 'h',
|
|
122
|
+
'Ĩ' => 'I',
|
|
123
|
+
'ĩ' => 'i',
|
|
124
|
+
'Ī' => 'I',
|
|
125
|
+
'ī' => 'i',
|
|
126
|
+
'Ĭ' => 'I',
|
|
127
|
+
'ĭ' => 'i',
|
|
128
|
+
'Į' => 'I',
|
|
129
|
+
'į' => 'i',
|
|
130
|
+
'ı' => 'i',
|
|
131
|
+
'IJ' => 'IJ',
|
|
132
|
+
'ij' => 'ij',
|
|
133
|
+
'Ĵ' => 'J',
|
|
134
|
+
'ĵ' => 'j',
|
|
135
|
+
'Ķ' => 'K',
|
|
136
|
+
'ķ' => 'k',
|
|
137
|
+
'ĸ' => 'k',
|
|
138
|
+
'Ĺ' => 'L',
|
|
139
|
+
'ĺ' => 'l',
|
|
140
|
+
'Ļ' => 'L',
|
|
141
|
+
'ļ' => 'l',
|
|
142
|
+
'Ľ' => 'L',
|
|
143
|
+
'ľ' => 'l',
|
|
144
|
+
'Ŀ' => 'L',
|
|
145
|
+
'ŀ' => 'l',
|
|
146
|
+
'Ł' => 'L',
|
|
147
|
+
'ł' => 'l',
|
|
148
|
+
'Ń' => 'N',
|
|
149
|
+
'ń' => 'n',
|
|
150
|
+
'Ņ' => 'N',
|
|
151
|
+
'ņ' => 'n',
|
|
152
|
+
'Ň' => 'N',
|
|
153
|
+
'ň' => 'n',
|
|
154
|
+
'ʼn' => "'n",
|
|
155
|
+
'Ŋ' => 'NG',
|
|
156
|
+
'ŋ' => 'ng',
|
|
157
|
+
'Ō' => 'O',
|
|
158
|
+
'ō' => 'o',
|
|
159
|
+
'Ŏ' => 'O',
|
|
160
|
+
'ŏ' => 'o',
|
|
161
|
+
'Ő' => 'O',
|
|
162
|
+
'ő' => 'o',
|
|
163
|
+
'Œ' => 'OE',
|
|
164
|
+
'œ' => 'oe',
|
|
165
|
+
'Ŕ' => 'R',
|
|
166
|
+
'ŕ' => 'r',
|
|
167
|
+
'Ŗ' => 'R',
|
|
168
|
+
'ŗ' => 'r',
|
|
169
|
+
'Ř' => 'R',
|
|
170
|
+
'ř' => 'r',
|
|
171
|
+
'Ś' => 'S',
|
|
172
|
+
'ś' => 's',
|
|
173
|
+
'Ŝ' => 'S',
|
|
174
|
+
'ŝ' => 's',
|
|
175
|
+
'Ş' => 'S',
|
|
176
|
+
'ş' => 's',
|
|
177
|
+
'Š' => 'S',
|
|
178
|
+
'š' => 's',
|
|
179
|
+
'Ţ' => 'T',
|
|
180
|
+
'ţ' => 't',
|
|
181
|
+
'Ť' => 'T',
|
|
182
|
+
'ť' => 't',
|
|
183
|
+
'Ŧ' => 'T',
|
|
184
|
+
'ŧ' => 't',
|
|
185
|
+
'Ũ' => 'U',
|
|
186
|
+
'ũ' => 'u',
|
|
187
|
+
'Ū' => 'U',
|
|
188
|
+
'ū' => 'u',
|
|
189
|
+
'Ŭ' => 'U',
|
|
190
|
+
'ŭ' => 'u',
|
|
191
|
+
'Ů' => 'U',
|
|
192
|
+
'ů' => 'u',
|
|
193
|
+
'Ű' => 'U',
|
|
194
|
+
'ű' => 'u',
|
|
195
|
+
'Ų' => 'U',
|
|
196
|
+
'ų' => 'u',
|
|
197
|
+
'Ŵ' => 'W',
|
|
198
|
+
'ŵ' => 'w',
|
|
199
|
+
'Ŷ' => 'Y',
|
|
200
|
+
'ŷ' => 'y',
|
|
201
|
+
'Ź' => 'Z',
|
|
202
|
+
'ź' => 'z',
|
|
203
|
+
'Ż' => 'Z',
|
|
204
|
+
'ż' => 'z',
|
|
205
|
+
'ž' => 'z',
|
|
206
|
+
'Ž' => 'Z',
|
|
207
|
+
}.freeze
|
|
208
|
+
end
|
|
209
|
+
end
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# When strings are mistakenly encoded as single-byte character sets, instead
|
|
4
|
+
# of UTF-8, there are some distinctive character combinations that we can spot
|
|
5
|
+
# and fix
|
|
6
|
+
# Useful table here http://www.i18nqa.com/debug/utf8-debug.html
|
|
7
|
+
module NameTamer
|
|
8
|
+
module Strings
|
|
9
|
+
module_function
|
|
10
|
+
|
|
11
|
+
# Strings that were wrongly encoded with single-byte encodings sometimes
|
|
12
|
+
# have tell-tale substrings that we can put back into the correct UTF-8
|
|
13
|
+
# character
|
|
14
|
+
def fix_encoding_errors(string)
|
|
15
|
+
string.gsub(BAD_ENCODING_PATTERNS) { |substring| BAD_ENCODING[substring] || substring }
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
BAD_ENCODING = {
|
|
19
|
+
"\xC3\x8D" => 'Í',
|
|
20
|
+
"\xC3\x8F" => 'Ï',
|
|
21
|
+
"\xC3\x90" => 'Ð',
|
|
22
|
+
"\xC3\x9D" => 'Ý',
|
|
23
|
+
'Â ' => ' ',
|
|
24
|
+
'¡' => '¡',
|
|
25
|
+
'¢' => '¢',
|
|
26
|
+
'£' => '£',
|
|
27
|
+
'¤' => '¤',
|
|
28
|
+
'Â¥' => '¥',
|
|
29
|
+
'¦' => '¦',
|
|
30
|
+
'§' => '§',
|
|
31
|
+
'¨' => '¨',
|
|
32
|
+
'©' => '©',
|
|
33
|
+
'ª' => 'ª',
|
|
34
|
+
'«' => '«',
|
|
35
|
+
'¬' => '¬',
|
|
36
|
+
'Â' => '',
|
|
37
|
+
'®' => '®',
|
|
38
|
+
'¯' => '¯',
|
|
39
|
+
'°' => '°',
|
|
40
|
+
'±' => '±',
|
|
41
|
+
'²' => '²',
|
|
42
|
+
'³' => '³',
|
|
43
|
+
'´' => '´',
|
|
44
|
+
'µ' => 'µ',
|
|
45
|
+
'¶' => '¶',
|
|
46
|
+
'·' => '·',
|
|
47
|
+
'¸' => '¸',
|
|
48
|
+
'¹' => '¹',
|
|
49
|
+
'º' => 'º',
|
|
50
|
+
'»' => '»',
|
|
51
|
+
'¼' => '¼',
|
|
52
|
+
'½' => '½',
|
|
53
|
+
'¾' => '¾',
|
|
54
|
+
'¿' => '¿',
|
|
55
|
+
'€' => '€',
|
|
56
|
+
'â„¢' => '™',
|
|
57
|
+
'â€' => '”', # Note the invisible Ux009D in the key
|
|
58
|
+
'†' => '†',
|
|
59
|
+
'‡' => '‡',
|
|
60
|
+
'•' => '•',
|
|
61
|
+
'…' => '…',
|
|
62
|
+
'‰' => '‰',
|
|
63
|
+
'′' => '′', # Manually added. Some seem to use this instead of Ux2019
|
|
64
|
+
'‹' => '‹',
|
|
65
|
+
'›' => '›',
|
|
66
|
+
'“' => '“',
|
|
67
|
+
'‚' => '‚',
|
|
68
|
+
'„' => '„',
|
|
69
|
+
'‘' => '‘',
|
|
70
|
+
'–' => '–',
|
|
71
|
+
'—' => '—',
|
|
72
|
+
'’' => '’',
|
|
73
|
+
'Ã ' => 'à',
|
|
74
|
+
'á' => 'á',
|
|
75
|
+
'â' => 'â',
|
|
76
|
+
'ã' => 'ã',
|
|
77
|
+
'ä' => 'ä',
|
|
78
|
+
'Ã¥' => 'å',
|
|
79
|
+
'æ' => 'æ',
|
|
80
|
+
'ç' => 'ç',
|
|
81
|
+
'è' => 'è',
|
|
82
|
+
'é' => 'é',
|
|
83
|
+
'ê' => 'ê',
|
|
84
|
+
'ë' => 'ë',
|
|
85
|
+
'ì' => 'ì',
|
|
86
|
+
'Ã' => 'í',
|
|
87
|
+
'î' => 'î',
|
|
88
|
+
'ï' => 'ï',
|
|
89
|
+
'ð' => 'ð',
|
|
90
|
+
'ñ' => 'ñ',
|
|
91
|
+
'ò' => 'ò',
|
|
92
|
+
'ó' => 'ó',
|
|
93
|
+
'ô' => 'ô',
|
|
94
|
+
'õ' => 'õ',
|
|
95
|
+
'ö' => 'ö',
|
|
96
|
+
'÷' => '÷',
|
|
97
|
+
'ø' => 'ø',
|
|
98
|
+
'ù' => 'ù',
|
|
99
|
+
'ú' => 'ú',
|
|
100
|
+
'û' => 'û',
|
|
101
|
+
'ü' => 'ü',
|
|
102
|
+
'ý' => 'ý',
|
|
103
|
+
'þ' => 'þ',
|
|
104
|
+
'ÿ' => 'ÿ',
|
|
105
|
+
'ß' => 'ß',
|
|
106
|
+
'ÃŒ' => 'Ì',
|
|
107
|
+
'Ü' => 'Ü',
|
|
108
|
+
'Ê' => 'Ê',
|
|
109
|
+
'Ú' => 'Ú',
|
|
110
|
+
'ÃŽ' => 'Î',
|
|
111
|
+
'Þ' => 'Þ',
|
|
112
|
+
'Ã' => 'Ã',
|
|
113
|
+
'È' => 'È',
|
|
114
|
+
'Ø' => 'Ø',
|
|
115
|
+
'Ö' => 'Ö',
|
|
116
|
+
'×' => '×',
|
|
117
|
+
'Ñ' => 'Ñ',
|
|
118
|
+
'Ã’' => 'Ò',
|
|
119
|
+
'Â' => 'Â',
|
|
120
|
+
'Ó' => 'Ó',
|
|
121
|
+
'Ô' => 'Ô',
|
|
122
|
+
'Ä' => 'Ä',
|
|
123
|
+
'Æ' => 'Æ',
|
|
124
|
+
'Ç' => 'Ç',
|
|
125
|
+
'Õ' => 'Õ',
|
|
126
|
+
'Ã…' => 'Å',
|
|
127
|
+
'É' => 'É',
|
|
128
|
+
'Ë' => 'Ë',
|
|
129
|
+
'Û' => 'Û',
|
|
130
|
+
'À' => 'À',
|
|
131
|
+
'Ù' => 'Ù',
|
|
132
|
+
'Ã�' => 'Á',
|
|
133
|
+
'Å ' => 'Š',
|
|
134
|
+
'Å¡' => 'š',
|
|
135
|
+
'Ÿ' => 'Ÿ',
|
|
136
|
+
'Ž' => 'Ž',
|
|
137
|
+
'ž' => 'ž',
|
|
138
|
+
'Å’' => 'Œ',
|
|
139
|
+
'Å“' => 'œ',
|
|
140
|
+
'Æ’' => 'ƒ',
|
|
141
|
+
'Ëœ' => '˜',
|
|
142
|
+
'ˆ' => 'ˆ',
|
|
143
|
+
"\x00" => '', # Manually added to avoid Bad Argument exception
|
|
144
|
+
}.freeze
|
|
145
|
+
|
|
146
|
+
BAD_ENCODING_PATTERNS = /(#{BAD_ENCODING.keys.join('|')})/
|
|
147
|
+
end
|
|
148
|
+
end
|