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.
Files changed (46) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +41 -0
  3. data/LICENSE +1 -1
  4. data/README.md +60 -34
  5. data/lib/name-tamer.rb +2 -0
  6. data/lib/name_tamer/constants/adfixes_prefix_person +44 -0
  7. data/lib/name_tamer/constants/adfixes_suffix_organization +213 -0
  8. data/lib/name_tamer/constants/adfixes_suffix_person +131 -0
  9. data/lib/name_tamer/constants.rb +28 -60
  10. data/lib/name_tamer/name/private_methods_nice_name.rb +116 -0
  11. data/lib/name_tamer/name/private_methods_simple_name.rb +71 -0
  12. data/lib/name_tamer/name/utilities.rb +84 -0
  13. data/lib/name_tamer/name.rb +13 -294
  14. data/lib/name_tamer/strings/approximations.rb +209 -0
  15. data/lib/name_tamer/strings/bad_encoding.rb +148 -0
  16. data/lib/name_tamer/strings/capitalization.rb +46 -0
  17. data/lib/name_tamer/strings/compound_names.rb +34 -0
  18. data/lib/name_tamer/strings/core.rb +62 -0
  19. data/lib/name_tamer/strings/name_modifiers.rb +51 -0
  20. data/lib/name_tamer/strings/spacing.rb +22 -0
  21. data/lib/name_tamer/strings.rb +9 -0
  22. data/lib/name_tamer/text.rb +21 -13
  23. data/lib/name_tamer/version.rb +3 -1
  24. data/lib/name_tamer.rb +2 -3
  25. metadata +26 -36
  26. data/.codeclimate.yml +0 -18
  27. data/.env +0 -1
  28. data/.gitignore +0 -26
  29. data/.hound.yml +0 -6
  30. data/.rspec +0 -2
  31. data/.rubocop.yml +0 -63
  32. data/.travis.yml +0 -13
  33. data/Gemfile +0 -20
  34. data/Guardfile +0 -16
  35. data/Rakefile +0 -14
  36. data/doc/maintenance.rake +0 -76
  37. data/doc/prefixes.csv +0 -49
  38. data/doc/suffixes.csv +0 -345
  39. data/lib/name_tamer/array.rb +0 -8
  40. data/lib/name_tamer/string.rb +0 -280
  41. data/name_tamer.gemspec +0 -19
  42. data/spec/name_tamer/name_spec.rb +0 -95
  43. data/spec/name_tamer/string_spec.rb +0 -5
  44. data/spec/name_tamer/text_spec.rb +0 -40
  45. data/spec/spec_helper.rb +0 -14
  46. data/spec/support/names.yml +0 -741
@@ -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!(ASCII_SPACE)
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
- ct_as_sym = new_contact_type.to_sym
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(&block)
83
+ def each_word(&)
84
84
  @words ||= slug.split(SLUG_DELIMITER)
85
- @words.each(&block)
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
- # Tidy up the name we've received
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