name_tamer 0.6.1 → 1.0.2

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.
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NameTamer
4
+ class Name
5
+ module Utilities
6
+ def contact_type_from(args)
7
+ args_ct = args[:contact_type]
8
+ return unless args_ct
9
+
10
+ ct = args_ct.is_a?(Symbol) ? args_ct : args_ct.dup
11
+ ct = ct.to_s unless [String, Symbol].include? ct.class
12
+ ct.downcase! if ct.instance_of?(String)
13
+ ct = ct.to_sym
14
+ ct = nil unless %i[person organization].include? ct
15
+
16
+ ct
17
+ end
18
+
19
+ # If we don't know the contact type, what's our best guess?
20
+ def contact_type_best_effort
21
+ if @contact_type
22
+ @contact_type
23
+ else
24
+ # If it's just one word we'll assume organization.
25
+ # If more then we'll assume a person
26
+ @name.include?(ASCII_SPACE) ? :person : :organization
27
+ end
28
+ end
29
+
30
+ # We pass to this routine either prefixes or suffixes
31
+ def remove_outermost_adfix(adfix_type, name_part)
32
+ ct, parts = find_contact_type_and_parts(ADFIX_PATTERNS[adfix_type], name_part)
33
+
34
+ return name_part unless @adfix_found
35
+
36
+ # If we've found a diagnostic adfix then set the contact type
37
+ self.contact_type = ct
38
+
39
+ # The remainder of the name will be in parts[0] or parts[2] depending
40
+ # on whether this is a prefix or a suffix.
41
+ # We'll also remove any trailing commas we've exposed.
42
+ (parts[0] + parts[2]).gsub(/\s*,\s*$/, '')
43
+ end
44
+
45
+ def find_contact_type_and_parts(adfixes, name_part)
46
+ ct = contact_type_best_effort
47
+ parts = name_part.partition adfixes[ct]
48
+ @adfix_found = !parts[1].empty?
49
+
50
+ return [ct, parts] if @contact_type || @adfix_found
51
+
52
+ # If the contact type is indeterminate and we didn't find a diagnostic adfix
53
+ # for a person then try again for an organization
54
+ ct = :organization
55
+ parts = name_part.partition adfixes[ct]
56
+ @adfix_found = !parts[1].empty?
57
+
58
+ [ct, parts]
59
+ end
60
+
61
+ # Original Version of NameCase:
62
+ # Copyright (c) Mark Summerfield 1998-2008. All Rights Reserved
63
+ # This module may be used/distributed/modified under the same terms as Perl itself
64
+ # http://dev.perl.org/licenses/ (GPL)
65
+ #
66
+ # Ruby Version:
67
+ # Copyright (c) Aaron Patterson 2006
68
+ # NameCase is distributed under the GPL license.
69
+ #
70
+ # Substantially modified for Xendata
71
+ # Improved in several areas, also now adds non-breaking spaces for
72
+ # compound names like "van der Pump"
73
+ def name_case(lowercase)
74
+ # We assume the name is passed already downcased
75
+ n = Strings.upcase_first_letter(lowercase)
76
+ n = Strings.downcase_after_apostrophe(n)
77
+ n = Strings.fix_mac(n)
78
+ n = Strings.fix_ff(n)
79
+ n = Strings.fix_name_modifiers(n)
80
+ Strings.upcase_initials(n)
81
+ end
82
+ end
83
+ end
84
+ end
@@ -1,5 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'name/private_methods_nice_name'
4
+ require_relative 'name/private_methods_simple_name'
5
+ require_relative 'name/utilities'
6
+
3
7
  module NameTamer
4
8
  class Name
5
9
  # References:
@@ -52,7 +56,7 @@ module NameTamer
52
56
  remove_periods_from_initials # "J.P.R. Williams" -> "JPR Williams"
53
57
  standardize_words # "B&Q Intl" -> "B and Q International"
54
58
 
55
- @simple_name.whitespace_to!(ASCII_SPACE)
59
+ @simple_name = Strings.whitespace_to(@simple_name, ASCII_SPACE)
56
60
  end
57
61
 
58
62
  @simple_name
@@ -72,228 +76,20 @@ module NameTamer
72
76
  end
73
77
 
74
78
  def contact_type=(new_contact_type)
75
- ct_as_sym = new_contact_type.to_sym
76
-
77
- unless @contact_type.nil? || @contact_type == ct_as_sym
78
- puts "Changing contact type of #{@name} from #{@contact_type} to #{new_contact_type}"
79
- end
80
-
81
- @contact_type = ct_as_sym
79
+ @contact_type = new_contact_type.to_sym
82
80
  end
83
81
 
84
82
  # Useful method for iterating through the words in the name
85
- def each_word(&block)
83
+ def each_word(&)
86
84
  @words ||= slug.split(SLUG_DELIMITER)
87
- @words.each(&block)
85
+ @words.each(&)
88
86
  end
89
87
 
90
- # These lines aren't used and aren't covered by specs
91
- # def name=(new_name)
92
- # initialize new_name, :contact_type => @contact_type
93
- # end
94
- #
95
- # def to_hash
96
- # {
97
- # name: name,
98
- # nice_name: nice_name,
99
- # simple_name: simple_name,
100
- # slug: slug,
101
- # contact_type: contact_type,
102
- # last_name: last_name,
103
- # remainder: remainder,
104
- # adfix_found: adfix_found
105
- # }
106
- # end
107
-
108
88
  private
109
89
 
110
- #--------------------------------------------------------
111
- # Tidy up the name we've received
112
- #--------------------------------------------------------
113
-
114
- def unescape
115
- @tidy_name.ensure_safe!.safe_unescape!.unescape_html!
116
- end
117
-
118
- def remove_zero_width
119
- @tidy_name.strip_unwanted!(ZERO_WIDTH_FILTER)
120
- end
121
-
122
- def tidy_spacing
123
- @tidy_name
124
- .space_around_comma!
125
- .strip_or_self!
126
- .whitespace_to!(ASCII_SPACE)
127
- end
128
-
129
- def fix_encoding_errors
130
- @tidy_name.fix_encoding_errors!
131
- end
132
-
133
- # Remove spaces from groups of initials
134
- def consolidate_initials
135
- @tidy_name
136
- .remove_spaces_from_initials!
137
- .ensure_space_after_initials!
138
- end
139
-
140
- # An adfix is either a prefix or a suffix
141
- def remove_adfixes
142
- if @last_name.nil?
143
- # Our name is still in one part, not two
144
- loop do
145
- @nice_name = remove_outermost_adfix(:suffix, @nice_name)
146
- break unless @adfix_found
147
- end
148
-
149
- loop do
150
- @nice_name = remove_outermost_adfix(:prefix, @nice_name)
151
- break unless @adfix_found
152
- end
153
- else
154
- # Our name is currently in two halves
155
- loop do
156
- @last_name = remove_outermost_adfix(:suffix, @last_name)
157
- break unless @adfix_found
158
- end
159
-
160
- loop do
161
- @remainder = remove_outermost_adfix(:prefix, @remainder)
162
- break unless @adfix_found
163
- end
164
- end
165
- end
166
-
167
- # Names in the form "Smith, John" need to be turned around to "John Smith"
168
- def fixup_last_name_first
169
- return if @contact_type == :organization
170
-
171
- parts = @nice_name.split ', '
172
-
173
- return unless parts.count == 2
174
-
175
- @last_name = parts[0] # Sometimes the last name alone is all caps and we can name-case it
176
- @remainder = parts[1]
177
- end
178
-
179
- # Sometimes we end up with mismatched braces after adfix stripping
180
- # e.g. "Ceres (Ceres Holdings LLC)" -> "Ceres (Ceres Holdings"
181
- def fixup_mismatched_braces
182
- left_brace_count = @nice_name.count '('
183
- right_brace_count = @nice_name.count ')'
184
-
185
- if left_brace_count > right_brace_count
186
- @nice_name += ')'
187
- elsif left_brace_count < right_brace_count
188
- @nice_name = '(' + @nice_name
189
- end
190
- end
191
-
192
- def name_wrangle
193
- # Fix case if all caps or all lowercase
194
- if @last_name.nil?
195
- name_wrangle_single_name
196
- else
197
- name_wrangle_split_name
198
- end
199
- end
200
-
201
- def name_wrangle_single_name
202
- lowercase = @nice_name.downcase
203
- uppercase = @nice_name.upcase
204
- fix_case = false
205
-
206
- if @contact_type == :organization
207
- fix_case = true if @nice_name == uppercase && @nice_name.length > 4
208
- elsif [uppercase, lowercase].include?(@nice_name)
209
- fix_case = true
210
- end
211
-
212
- @nice_name = name_case(lowercase) if fix_case
213
- end
214
-
215
- def name_wrangle_split_name
216
- # It's a person if we've split the name, so no organization logic here
217
- lowercase = @last_name.downcase
218
- uppercase = @last_name.upcase
219
- @last_name = name_case(lowercase) if [uppercase, lowercase].include?(@last_name)
220
- @nice_name = +"#{@remainder} #{@last_name}"
221
- end
222
-
223
- # Conjoin compound names with non-breaking spaces
224
- def use_nonbreaking_spaces_in_compound_names
225
- @nice_name
226
- .nbsp_in_compound_name!
227
- .nbsp_in_name_modifier!
228
- end
229
-
230
- #--------------------------------------------------------
231
- # Make search name from nice name
232
- #--------------------------------------------------------
233
-
234
- # Remove initials from personal names unless they are the only identifier.
235
- # i.e. only remove initials if there's also a proper name there
236
- def remove_initials
237
- return unless @contact_type == :person
238
-
239
- temp_name = @simple_name.gsub(/\b([a-z](?:\.*\s+|\.))/i, '')
240
-
241
- # If the name still has at least one space we're OK
242
- @simple_name = temp_name if temp_name.include?(ASCII_SPACE)
243
- end
244
-
245
- def remove_middle_names
246
- return unless @contact_type == :person
247
-
248
- first_name, parts = find_first_usable_name(@simple_name.split)
249
- last_name, = find_last_usable_name(parts)
250
-
251
- return unless first_name || last_name
252
-
253
- separator = first_name && last_name ? ' ' : ''
254
- @simple_name = +"#{first_name}#{separator}#{last_name}"
255
- end
256
-
257
- def find_first_usable_name(parts)
258
- part = nil
259
-
260
- parts.each_index do |i|
261
- part = parts[i]
262
- next if part.gsub(FILTER_COMPAT, '').empty?
263
- parts = parts.slice(i + 1, parts.length) # don't use "slice!"
264
- break
265
- end
266
-
267
- [part, parts]
268
- end
269
-
270
- def find_last_usable_name(parts)
271
- part = nil
272
-
273
- parts.reverse_each do |p|
274
- next if p.gsub(FILTER_COMPAT, '').empty?
275
- part = p
276
- break
277
- end
278
-
279
- part
280
- end
281
-
282
- def remove_periods_from_initials
283
- @simple_name.remove_periods_from_initials!
284
- end
285
-
286
- def standardize_words
287
- @simple_name.gsub!(/ *& */, ' and ') # replace ampersand characters with ' and '
288
- @simple_name.gsub!(/ *\+ */, ' plus ') # replace plus signs with ' plus '
289
- @simple_name.gsub!(/\bintl\b/i, 'International') # replace 'intl' with 'International'
290
- @simple_name.gsub!(/[־‐‑‒–—―−﹘﹣-]/, SLUG_DELIMITER) # Replace Unicode dashes with ASCII hyphen
291
- @simple_name.strip_unwanted!(/["“”™℠®©℗]/) # remove quotes and commercial decoration
292
- end
293
-
294
- #--------------------------------------------------------
295
- # Initialization and utilities
296
- #--------------------------------------------------------
90
+ include Name::PrivateMethodsNiceName
91
+ include Name::PrivateMethodsSimpleName
92
+ include Name::Utilities
297
93
 
298
94
  def initialize(new_name, args = {})
299
95
  @name = new_name || ''
@@ -309,84 +105,5 @@ module NameTamer
309
105
 
310
106
  @adfix_found = false
311
107
  end
312
-
313
- def contact_type_from(args)
314
- args_ct = args[:contact_type]
315
- return unless args_ct
316
-
317
- ct = args_ct.is_a?(Symbol) ? args_ct : args_ct.dup
318
- ct = ct.to_s unless [String, Symbol].include? ct.class
319
- ct.downcase! if ct.class == String
320
- ct = ct.to_sym
321
- ct = nil unless %i[person organization].include? ct
322
-
323
- ct
324
- end
325
-
326
- # If we don't know the contact type, what's our best guess?
327
- def contact_type_best_effort
328
- if @contact_type
329
- @contact_type
330
- else
331
- # If it's just one word we'll assume organization.
332
- # If more then we'll assume a person
333
- @name.include?(ASCII_SPACE) ? :person : :organization
334
- end
335
- end
336
-
337
- # We pass to this routine either prefixes or suffixes
338
- def remove_outermost_adfix(adfix_type, name_part)
339
- ct, parts = find_contact_type_and_parts(ADFIX_PATTERNS[adfix_type], name_part)
340
-
341
- return name_part unless @adfix_found
342
-
343
- # If we've found a diagnostic adfix then set the contact type
344
- self.contact_type = ct
345
-
346
- # The remainder of the name will be in parts[0] or parts[2] depending
347
- # on whether this is a prefix or a suffix.
348
- # We'll also remove any trailing commas we've exposed.
349
- (parts[0] + parts[2]).gsub(/\s*,\s*$/, '')
350
- end
351
-
352
- def find_contact_type_and_parts(adfixes, name_part)
353
- ct = contact_type_best_effort
354
- parts = name_part.partition adfixes[ct]
355
- @adfix_found = !parts[1].empty?
356
-
357
- return [ct, parts] if @contact_type || @adfix_found
358
-
359
- # If the contact type is indeterminate and we didn't find a diagnostic adfix
360
- # for a person then try again for an organization
361
- ct = :organization
362
- parts = name_part.partition adfixes[ct]
363
- @adfix_found = !parts[1].empty?
364
-
365
- [ct, parts]
366
- end
367
-
368
- # Original Version of NameCase:
369
- # Copyright (c) Mark Summerfield 1998-2008. All Rights Reserved
370
- # This module may be used/distributed/modified under the same terms as Perl itself
371
- # http://dev.perl.org/licenses/ (GPL)
372
- #
373
- # Ruby Version:
374
- # Copyright (c) Aaron Patterson 2006
375
- # NameCase is distributed under the GPL license.
376
- #
377
- # Substantially modified for Xendata
378
- # Improved in several areas, also now adds non-breaking spaces for
379
- # compound names like "van der Pump"
380
- def name_case(lowercase)
381
- n = lowercase.dup # We assume the name is passed already downcased
382
-
383
- n
384
- .upcase_first_letter!
385
- .downcase_after_apostrophe!
386
- .fix_mac!
387
- .fix_ff!
388
- .fix_name_modifiers!
389
- .upcase_initials!
390
- end
391
108
  end
392
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
+ extend self
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