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,8 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module NameTamer
2
- NONBREAKING_SPACE = "\u00a0".freeze
3
- ASCII_SPACE = ' '.freeze
4
+ NONBREAKING_SPACE = "\u00a0"
5
+ ASCII_SPACE = ' '
4
6
  ADFIX_JOINERS = "[#{ASCII_SPACE}-]".freeze
5
- SLUG_DELIMITER = '-'.freeze
7
+ SLUG_DELIMITER = '-'
6
8
  ZERO_WIDTH_FILTER = /[\u180E\u200B\u200C\u200D\u2063\uFEFF]/
7
9
 
8
10
  # Constants for parameterizing Unicode strings for IRIs
@@ -34,11 +36,11 @@ module NameTamer
34
36
  # We're using the most restrictive segment definition (isegment-nz-nc)
35
37
  # to avoid any possible problems with the IRI that it one day might
36
38
  # get placed in.
37
- ALPHA = 'A-Za-z'.freeze
38
- DIGIT = '0-9'.freeze
39
- UCSCHAR = '\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF'.freeze
39
+ ALPHA = 'A-Za-z'
40
+ DIGIT = '0-9'
41
+ UCSCHAR = '\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF'
40
42
  IUNRESERVED = "#{ALPHA}#{DIGIT}\\-\\._~#{UCSCHAR}".freeze
41
- SUBDELIMS = '!$&\'\(\)\*+,;='.freeze
43
+ SUBDELIMS = '!$&\'\(\)\*+,;='
42
44
  ISEGMENT_NZ_NC = "#{IUNRESERVED}#{SUBDELIMS}@".freeze # pct-encoded not needed
43
45
  FILTER_RFC3987 = /[^#{ISEGMENT_NZ_NC}]/
44
46
  FILTER_COMPAT = /[^#{ALPHA}#{DIGIT}\-_#{UCSCHAR}]/
@@ -47,75 +49,41 @@ module NameTamer
47
49
  # If you add to the list, you can use spaces and dots where appropriate
48
50
  # Ensure any single letters are followed by a dot because we'll add one to the string
49
51
  # during processing, e.g. "y Cia." should be "y. Cia."
52
+
53
+ private_class_method def self.get_constants_from(filename)
54
+ File.readlines(Pathname.new(__dir__).join('constants', filename), chomp: true).reject(&:empty?)
55
+ end
56
+
50
57
  ADFIXES = {
51
58
  prefix: {
52
- person: [
53
- 'Baroness', 'Capt.', 'Captain', 'Col.', 'Colonel', 'Dame', 'Doctor',
54
- 'Dr.', 'Judge', 'Justice', 'Lady', 'Lieut.', 'Lieutenant', 'Lord',
55
- 'Madame', 'Major', 'Master', 'Matron', 'Messrs.', 'Mgr.', 'Miss',
56
- 'Mister', 'Mlle.', 'Mme.', 'Mons.', 'Mr.', 'Mr. & Mrs.', 'Mr. and Mrs.',
57
- 'Mrs.', 'Ms.', 'Msgr.', 'Prof.', 'Professor', 'Rev.', 'Reverend', 'Sir',
58
- 'Sister', 'The Hon.', 'The Lady.', 'The Lord', 'The Rt. Hon.', 'Doktor',
59
- 'Herr', 'Frau'
60
- ],
59
+ person: get_constants_from('adfixes_prefix_person'),
61
60
  organization: [
62
- 'Fa.', 'P.T.', 'P.T. Tbk.', 'U.D.'
61
+ 'Fa.',
62
+ 'P.T. Tbk.',
63
+ 'P.T.',
64
+ 'U.D.',
63
65
  ],
64
66
  before: '\\A', after: ADFIX_JOINERS
65
67
  },
66
68
  suffix: {
67
- person: [
68
- 'Chartered F.C.S.I.', 'Chartered M.C.S.I.', 'I.F.R.S. Certified', 'F.Inst.L.M.', 'C.I.S.S.P.', 'F.C.I.P.S.',
69
- 'M.R.I.C.S.', 'T.M.I.E.T.', 'Dip. D.M.', 'A.A.M.S.', 'A.C.C.A.', 'A.C.M.A.', 'A.I.F.A.', 'A.W.M.A.', 'C.A.I.A.',
70
- 'C.A.P.M.', 'C.C.I.M.', 'C.D.F.A.', 'C.E.P.P.', 'C.F.B.S.', 'C.G.M.A.', 'C.I.T.P.', 'C.L.T.C.', 'C.P.C.C.',
71
- 'C.R.P.C.', 'C.R.P.S.', 'C.S.O.X.', 'C.S.S.D.', 'F.B.C.S.', 'F.C.C.A.', 'F.C.M.I.', 'F.C.S.I.', 'F.I.E.T.',
72
- 'F.I.R.P.', 'M.I.E.T.', 'M.S.F.S.', 'M.Sc. D.', 'O.R.S.C.', 'R.I.C.P.', 'B.Tech.', 'Cantab.', 'Ch.F.C.',
73
- 'D.Phil.', 'I.T.I.L. v3', 'M.Io.D.', 'S.C.M.P', 'A.C.A.', 'A.C.C.', 'A.E.P.', 'A.I.F.', 'A.S.A.', 'B.Eng.',
74
- 'C.B.V.', 'C.E.M.', 'C.Eng.', 'C.F.A.', 'C.F.F.', 'C.F.P.', 'C.F.S.', 'C.G.A.', 'C.G.B.', 'C.G.P.', 'C.I.M.',
75
- 'C.L.P.', 'C.L.U.', 'C.M.A.', 'C.M.T.', 'C.P.A.', 'C.T.A.', 'C.W.S.', 'D.B.E.', 'D.D.S.', 'D.V.M.', 'E.R.P.',
76
- 'Eng.D.', 'F.C.A.', 'F.P.C.', 'F.R.M.', 'F.R.M.', 'G.S.P.', 'L.P.S.', 'M.B.A.', 'M.B.E.', 'M.E.P.', 'M.Eng.',
77
- 'M.Jur.', 'M.P.A.', 'M.S.F.', 'M.S.P.', 'O.B.E.', 'P.C.C.', 'P.F.S.', 'P.H.R.', 'P.M.C.', 'P.M.P.', 'P.M.P.',
78
- 'P.S.P.', 'R.F.C.', 'V.M.D.', 'B.Ed.', 'B.Sc.', 'Ed.D.', 'Ed.M.', 'Hons.', 'LL.B.', 'LL.D.', 'LL.M.', 'M.Ed.',
79
- 'M.Sc.', 'Oxon.', 'Ph.D.', 'B.A.', 'C.A.', 'E.A.', 'Esq.', 'J.D.', 'K.C.', 'M.A.', 'M.D.', 'M.P.', 'M.S.',
80
- 'O.K.', 'P.A.', 'Q.C.', 'R.D.', 'III', 'Jr.', 'Sr.', 'II', 'IV', 'V'
81
- ],
82
- organization: [
83
- 'S. de R.L. de C.V.', 'S.A.P.I. de C.V.', 'y. Cía. S. en C.', 'Private Limited', 'S.M. Pte. Ltd.',
84
- 'Cía. S. C. A.', 'y. Cía. S. C.', 'S.A. de C.V.', 'spol. s.r.o.', '(Pty.) Ltd.', '(Pvt.) Ltd.', 'A.D.S.I.Tz.',
85
- 'S.p. z.o.o.', '(Pvt.)Ltd.', 'akc. spol.', 'Cía. Ltda.', 'E.B.V.B.A.', 'P. Limited', 'S. de R.L.', 'S.I.C.A.V.',
86
- 'S.P.R.L.U.', 'А.Д.С.И.Ц.', '(P.) Ltd.', 'C. por A.', 'Comm.V.A.', 'Ltd. Şti.', 'Plc. Ltd.', 'Pte. Ltd.',
87
- 'Pty. Ltd.', 'Pvt. Ltd.', 'Soc. Col.', 'A.M.B.A.', 'A.S.B.L.', 'A.V.E.E.', 'B.V.B.A.', 'B.V.I.O.', 'C.V.B.A.',
88
- 'C.V.O.A.', 'E.E.I.G.', 'E.I.R.L.', 'E.O.O.D.', 'E.U.R.L.', 'F.M.B.A.', 'G.m.b.H.', 'Ges.b.R.', 'K.G.a.A.',
89
- 'L.L.L.P.', 'Ltd. Co.', 'Ltd. Co.', 'M.E.P.E.', 'n.y.r.t.', 'O.V.E.E.', 'P.E.E.C.', 'P.L.L.C.', 'P.L.L.C.',
90
- 'S. en C.', 'S.a.p.a.', 'S.A.R.L.', 'S.à.R.L.', 'S.A.S.U.', 'S.C.e.I.', 'S.C.O.P.', 'S.C.p.A.', 'S.C.R.I.',
91
- 'S.C.R.L.', 'S.M.B.A.', 'S.P.R.L.', 'Е.О.О.Д.', '&. Cie.', 'and Co.', 'Comm.V.', 'Limited', 'P. Ltd.',
92
- 'Part.G.', 'Sh.p.k.', '&. Co.', 'C.X.A.', 'd.n.o.', 'd.o.o.', 'E.A.D.', 'e.h.f.', 'E.P.E.', 'E.S.V.', 'F.C.P.',
93
- 'F.I.E.', 'G.b.R.', 'G.I.E.', 'G.M.K.', 'G.S.K.', 'H.U.F.', 'K.D.A.', 'k.f.t.', 'k.h.t.', 'k.k.t.', 'L.L.C.',
94
- 'L.L.P.', 'o.h.f.', 'O.H.G.', 'O.O.D.', 'O.y.j.', 'p.l.c.', 'P.S.U.', 'S.A.E.', 'S.A.S.', 'S.C.A.', 'S.C.E.',
95
- 'S.C.S.', 'S.E.M.', 'S.E.P.', 's.e.s.', 'S.G.R.', 'S.N.C.', 'S.p.A.', 'S.P.E.', 'S.R.L.', 's.r.o.', 'Unltd.',
96
- 'V.O.F.', 'V.o.G.', 'v.o.s.', 'V.Z.W.', 'z.r.t.', 'А.А.Т.', 'Е.А.Д.', 'З.А.Т.', 'К.Д.А.', 'О.О.Д.', 'Т.А.А.',
97
- '股份有限公司', 'Ap.S.', 'Corp.', 'ltda.', 'Sh.A.', 'st.G.', 'Ultd.', 'a.b.', 'A.D.', 'A.E.', 'A.G.', 'A.S.',
98
- 'A.Ş.', 'A.y.', 'B.M.', 'b.t.', 'B.V.', 'C.A.', 'C.V.', 'd.d.', 'e.c.', 'E.E.', 'e.G.', 'E.I.', 'E.P.', 'E.T.',
99
- 'E.U.', 'e.v.', 'G.K.', 'G.P.', 'h.f.', 'Inc.', 'K.D.', 'K.G.', 'K.K.', 'k.s.', 'k.v.', 'K.y.', 'L.C.', 'L.P.',
100
- 'Ltd.', 'N.K.', 'N.L.', 'N.V.', 'O.E.', 'O.G.', 'O.Ü.', 'O.y.', 'P.C.', 'p.l.', 'Pty.', 'PUP.', 'Pvt.', 'r.t.',
101
- 'S.A.', 'S.D.', 'S.E.', 's.f.', 'S.L.', 'S.P.', 'S.s.', 'T.K.', 'T.Ü.', 'U.Ü.', 'Y.K.', 'А.Д.', 'І.П.', 'К.Д.',
102
- 'ПУП.', 'С.Д.', 'בע"מ', '任意組合', '匿名組合', '合同会社', '合名会社', '合資会社', '有限会社', '有限公司', '株式会社',
103
- 'A/S', 'G/S', 'I/S', 'K/S', 'P/S', 'S/A'
104
- ],
69
+ person: get_constants_from('adfixes_suffix_person'),
70
+ organization: get_constants_from('adfixes_suffix_organization'),
105
71
  before: ADFIX_JOINERS, after: '\\z'
106
- }
72
+ },
107
73
  }.freeze
108
74
 
109
- ADFIX_PATTERNS = Hash[%i[prefix suffix].map do |adfix_type|
75
+ CONTACT_TYPES = %i[person organization].freeze
76
+
77
+ ADFIX_PATTERNS = %i[prefix suffix].to_h do |adfix_type|
110
78
  patterns = {}
111
79
  adfix = ADFIXES[adfix_type]
112
80
 
113
- %i[person organization].each do |ct|
81
+ CONTACT_TYPES.each do |ct|
114
82
  with_optional_spaces = adfix[ct].map { |p| p.gsub(ASCII_SPACE, ' *') }
115
83
  pattern_string = with_optional_spaces.join('|').gsub('.', '\.*')
116
- patterns[ct] = /#{adfix[:before]}\(*(?:#{pattern_string})[®™\)]*#{adfix[:after]}/i
84
+ patterns[ct] = /#{adfix[:before]}\(*(?:#{pattern_string})[®™)]*#{adfix[:after]}/i
117
85
  end
118
86
 
119
87
  [adfix_type, patterns]
120
- end]
88
+ end
121
89
  end
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NameTamer
4
+ class Name
5
+ module PrivateMethodsNiceName
6
+ def unescape
7
+ @tidy_name = Strings.unescape_html(Strings.safe_unescape(Strings.ensure_safe(@tidy_name)))
8
+ end
9
+
10
+ def remove_zero_width
11
+ @tidy_name = Strings.strip_unwanted(@tidy_name, ZERO_WIDTH_FILTER)
12
+ end
13
+
14
+ def tidy_spacing
15
+ @tidy_name = Strings.whitespace_to(Strings.space_around_comma(@tidy_name).strip, ASCII_SPACE)
16
+ end
17
+
18
+ def fix_encoding_errors
19
+ @tidy_name = Strings.fix_encoding_errors(@tidy_name)
20
+ end
21
+
22
+ # Remove spaces from groups of initials
23
+ def consolidate_initials
24
+ @tidy_name = Strings.ensure_space_after_initials(Strings.remove_spaces_from_initials(@tidy_name))
25
+ end
26
+
27
+ # An adfix is either a prefix or a suffix
28
+ def remove_adfixes
29
+ if @last_name.nil?
30
+ # Our name is still in one part, not two
31
+ loop do
32
+ @nice_name = remove_outermost_adfix(:suffix, @nice_name)
33
+ break unless @adfix_found
34
+ end
35
+
36
+ loop do
37
+ @nice_name = remove_outermost_adfix(:prefix, @nice_name)
38
+ break unless @adfix_found
39
+ end
40
+ else
41
+ # Our name is currently in two halves
42
+ loop do
43
+ @last_name = remove_outermost_adfix(:suffix, @last_name)
44
+ break unless @adfix_found
45
+ end
46
+
47
+ loop do
48
+ @remainder = remove_outermost_adfix(:prefix, @remainder)
49
+ break unless @adfix_found
50
+ end
51
+ end
52
+ end
53
+
54
+ # Names in the form "Smith, John" need to be turned around to "John Smith"
55
+ def fixup_last_name_first
56
+ return if @contact_type == :organization
57
+
58
+ parts = @nice_name.split ', '
59
+
60
+ return unless parts.count == 2
61
+
62
+ @last_name = parts[0] # Sometimes the last name alone is all caps and we can name-case it
63
+ @remainder = parts[1]
64
+ end
65
+
66
+ # Sometimes we end up with mismatched braces after adfix stripping
67
+ # e.g. "Ceres (Ceres Holdings LLC)" -> "Ceres (Ceres Holdings"
68
+ def fixup_mismatched_braces
69
+ left_brace_count = @nice_name.count '('
70
+ right_brace_count = @nice_name.count ')'
71
+
72
+ if left_brace_count > right_brace_count
73
+ @nice_name += ')'
74
+ elsif left_brace_count < right_brace_count
75
+ @nice_name = "(#{@nice_name}"
76
+ end
77
+ end
78
+
79
+ def name_wrangle
80
+ # Fix case if all caps or all lowercase
81
+ if @last_name.nil?
82
+ name_wrangle_single_name
83
+ else
84
+ name_wrangle_split_name
85
+ end
86
+ end
87
+
88
+ def name_wrangle_single_name
89
+ lowercase = @nice_name.downcase
90
+ uppercase = @nice_name.upcase
91
+ fix_case = false
92
+
93
+ if @contact_type == :organization
94
+ fix_case = true if @nice_name == uppercase && @nice_name.length > 4
95
+ elsif [uppercase, lowercase].include?(@nice_name)
96
+ fix_case = true
97
+ end
98
+
99
+ @nice_name = name_case(lowercase) if fix_case
100
+ end
101
+
102
+ def name_wrangle_split_name
103
+ # It's a person if we've split the name, so no organization logic here
104
+ lowercase = @last_name.downcase
105
+ uppercase = @last_name.upcase
106
+ @last_name = name_case(lowercase) if [uppercase, lowercase].include?(@last_name)
107
+ @nice_name = "#{@remainder} #{@last_name}"
108
+ end
109
+
110
+ # Conjoin compound names with non-breaking spaces
111
+ def use_nonbreaking_spaces_in_compound_names
112
+ @nice_name = Strings.nbsp_in_name_modifier(Strings.nbsp_in_compound_name(@nice_name))
113
+ end
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NameTamer
4
+ class Name
5
+ module PrivateMethodsSimpleName
6
+ # Remove initials from personal names unless they are the only identifier.
7
+ # i.e. only remove initials if there's also a proper name there
8
+ def remove_initials
9
+ return unless @contact_type == :person
10
+
11
+ temp_name = @simple_name.gsub(/\b([a-z](?:\.*\s+|\.))/i, '')
12
+
13
+ # If the name still has at least one space we're OK
14
+ @simple_name = temp_name if temp_name.include?(ASCII_SPACE)
15
+ end
16
+
17
+ def remove_middle_names
18
+ return unless @contact_type == :person
19
+
20
+ first_name, parts = find_first_usable_name(@simple_name.split)
21
+ last_name, = find_last_usable_name(parts)
22
+
23
+ return unless first_name || last_name
24
+
25
+ separator = first_name && last_name ? ' ' : ''
26
+ @simple_name = "#{first_name}#{separator}#{last_name}"
27
+ end
28
+
29
+ def find_first_usable_name(parts)
30
+ part = nil
31
+
32
+ parts.each_index do |i|
33
+ part = parts[i]
34
+ next if part.gsub(FILTER_COMPAT, '').empty?
35
+
36
+ parts = parts.slice(i + 1, parts.length) # don't use "slice!"
37
+ break
38
+ end
39
+
40
+ [part, parts]
41
+ end
42
+
43
+ def find_last_usable_name(parts)
44
+ part = nil
45
+
46
+ parts.reverse_each do |p|
47
+ next if p.gsub(FILTER_COMPAT, '').empty?
48
+
49
+ part = p
50
+ break
51
+ end
52
+
53
+ part
54
+ end
55
+
56
+ def remove_periods_from_initials
57
+ @simple_name = Strings.remove_periods_from_initials(@simple_name)
58
+ end
59
+
60
+ def standardize_words
61
+ words = @simple_name
62
+ .gsub(/ *& */, ' and ') # replace ampersand characters with ' and '
63
+ .gsub(/ *\+ */, ' plus ') # replace plus signs with ' plus '
64
+ .gsub(/\bintl\b/i, 'International') # replace 'intl' with 'International'
65
+ .gsub(/[־‐‑‒–—―−﹘﹣-]/, SLUG_DELIMITER) # Replace Unicode dashes with ASCII hyphen
66
+
67
+ @simple_name = Strings.strip_unwanted(words, /["“”™℠®©℗]/) # remove quotes and commercial decoration
68
+ end
69
+ end
70
+ end
71
+ end
@@ -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