Module: NameTamer::Strings

Defined in:
lib/name_tamer/strings/core.rb,
lib/name_tamer/strings/spacing.rb,
lib/name_tamer/strings/bad_encoding.rb,
lib/name_tamer/strings/approximations.rb,
lib/name_tamer/strings/capitalization.rb,
lib/name_tamer/strings/compound_names.rb,
lib/name_tamer/strings/name_modifiers.rb

Overview

Pure string transformations used throughout the gem. Every method takes a string and returns a new string; arguments are never mutated.

Constant Summary collapse

BAD_ENCODING =
{
  "\xC3\x8D" => 'Í',
  "\xC3\x8F" => 'Ï',
  "\xC3\x90" => 'Ð',
  "\xC3\x9D" => 'Ý',
  ' ' => ' ',
  '¡' => '¡',
  '¢' => '¢',
  '£' => '£',
  '¤' => '¤',
  'Â¥' => '¥',
  '¦' => '¦',
  '§' => '§',
  '¨' => '¨',
  '©' => '©',
  'ª' => 'ª',
  '«' => '«',
  '¬' => '¬',
  '­' => '­',
  '®' => '®',
  '¯' => '¯',
  '°' => '°',
  '±' => '±',
  '²' => '²',
  '³' => '³',
  '´' => '´',
  'µ' => 'µ',
  '¶' => '',
  '·' => '·',
  '¸' => '¸',
  '¹' => '¹',
  'º' => 'º',
  '»' => '»',
  '¼' => '¼',
  '½' => '½',
  '¾' => '¾',
  '¿' => '¿',
  '€' => '',
  'â„¢' => '',
  '”' => '', # Note the invisible Ux009D in the key
  '†' => '',
  '‡' => '',
  '•' => '',
  '…' => '',
  '‰' => '',
  '′' => '', # Manually added. Some seem to use this instead of Ux2019
  '‹' => '',
  '›' => '',
  '“' => '',
  '‚' => '',
  '„' => '',
  '‘' => '',
  '–' => '',
  '—' => '',
  '’' => '',
  'à' => 'à',
  'á' => 'á',
  'â' => 'â',
  'ã' => 'ã',
  'ä' => 'ä',
  'Ã¥' => 'å',
  'æ' => 'æ',
  'ç' => 'ç',
  'è' => 'è',
  'é' => 'é',
  'ê' => 'ê',
  'ë' => 'ë',
  'ì' => 'ì',
  'í' => 'í',
  'î' => 'î',
  'ï' => 'ï',
  'ð' => 'ð',
  'ñ' => 'ñ',
  'ò' => 'ò',
  'ó' => 'ó',
  'ô' => 'ô',
  'õ' => 'õ',
  'ö' => 'ö',
  '÷' => '÷',
  'ø' => 'ø',
  'ù' => 'ù',
  'ú' => 'ú',
  'û' => 'û',
  'ü' => 'ü',
  'ý' => 'ý',
  'þ' => 'þ',
  'ÿ' => 'ÿ',
  'ß' => 'ß',
  'ÃŒ' => 'Ì',
  'Ü' => 'Ü',
  'Ê' => 'Ê',
  'Ú' => 'Ú',
  'ÃŽ' => 'Î',
  'Þ' => 'Þ',
  'Ã' => 'Ã',
  'È' => 'È',
  'Ø' => 'Ø',
  'Ö' => 'Ö',
  '×' => '×',
  'Ñ' => 'Ñ',
  'Ã’' => 'Ò',
  'Â' => 'Â',
  'Ó' => 'Ó',
  'Ô' => 'Ô',
  'Ä' => 'Ä',
  'Æ' => 'Æ',
  'Ç' => 'Ç',
  'Õ' => 'Õ',
  'Ã…' => 'Å',
  'É' => 'É',
  'Ë' => 'Ë',
  'Û' => 'Û',
  'À' => 'À',
  'Ù' => 'Ù',
  'Ã�' => 'Á',
  'Å ' => 'Š',
  'Å¡' => 'š',
  'Ÿ' => 'Ÿ',
  'Ž' => 'Ž',
  'ž' => 'ž',
  'Å’' => 'Œ',
  'Å“' => 'œ',
  'Æ’' => 'ƒ',
  'Ëœ' => '˜',
  'ˆ' => 'ˆ',
  "\x00" => '', # Manually added to avoid Bad Argument exception
}.freeze
BAD_ENCODING_PATTERNS =
/(#{BAD_ENCODING.keys.join('|')})/
APPROXIMATIONS =
{
  'İ' => 'I',
  '×' => 'x',
  'ß' => 'ss',
  'À' => 'A',
  'à' => 'a',
  'Á' => 'A',
  'á' => 'a',
  'Â' => 'A',
  'â' => 'a',
  'Ã' => 'A',
  'ã' => 'a',
  'Ä' => 'A',
  'ä' => 'a',
  'Å' => 'A',
  'å' => 'a',
  'Æ' => 'AE',
  'æ' => 'ae',
  'Ç' => 'C',
  'ç' => 'c',
  'È' => 'E',
  'è' => 'e',
  'É' => 'E',
  'é' => 'e',
  'Ê' => 'E',
  'ê' => 'e',
  'Ë' => 'E',
  'ë' => 'e',
  'Ì' => 'I',
  'ì' => 'i',
  'Í' => 'I',
  'í' => 'i',
  'Î' => 'I',
  'î' => 'i',
  'Ï' => 'I',
  'ï' => 'i',
  'Ð' => 'D',
  'ð' => 'd',
  'Ñ' => 'N',
  'ñ' => 'n',
  'Ò' => 'O',
  'ò' => 'o',
  'Ó' => 'O',
  'ó' => 'o',
  'Ô' => 'O',
  'ô' => 'o',
  'Õ' => 'O',
  'õ' => 'o',
  'Ö' => 'O',
  'ö' => 'o',
  'Ø' => 'O',
  'ø' => 'o',
  'Ù' => 'U',
  'ù' => 'u',
  'Ú' => 'U',
  'ú' => 'u',
  'Û' => 'U',
  'û' => 'u',
  'Ü' => 'U',
  'ü' => 'u',
  'Ý' => 'Y',
  'ý' => 'y',
  'Þ' => 'Th',
  'þ' => 'th',
  'ÿ' => 'y',
  'Ÿ' => 'Y',
  'Ā' => 'A',
  'ā' => 'a',
  'Ă' => 'A',
  'ă' => 'a',
  'Ą' => 'A',
  'ą' => 'a',
  'Ć' => 'C',
  'ć' => 'c',
  'Ĉ' => 'C',
  'ĉ' => 'c',
  'Ċ' => 'C',
  'ċ' => 'c',
  'Č' => 'C',
  'č' => 'c',
  'Ď' => 'D',
  'ď' => 'd',
  'Đ' => 'D',
  'đ' => 'd',
  'Ē' => 'E',
  'ē' => 'e',
  'Ĕ' => 'E',
  'ĕ' => 'e',
  'Ė' => 'E',
  'ė' => 'e',
  'Ę' => 'E',
  'ę' => 'e',
  'Ě' => 'E',
  'ě' => 'e',
  'Ĝ' => 'G',
  'ĝ' => 'g',
  'Ğ' => 'G',
  'ğ' => 'g',
  'Ġ' => 'G',
  'ġ' => 'g',
  'Ģ' => 'G',
  'ģ' => 'g',
  'Ĥ' => 'H',
  'ĥ' => 'h',
  'Ħ' => 'H',
  'ħ' => 'h',
  'Ĩ' => 'I',
  'ĩ' => 'i',
  'Ī' => 'I',
  'ī' => 'i',
  'Ĭ' => 'I',
  'ĭ' => 'i',
  'Į' => 'I',
  'į' => 'i',
  'ı' => 'i',
  'IJ' => 'IJ',
  'ij' => 'ij',
  'Ĵ' => 'J',
  'ĵ' => 'j',
  'Ķ' => 'K',
  'ķ' => 'k',
  'ĸ' => 'k',
  'Ĺ' => 'L',
  'ĺ' => 'l',
  'Ļ' => 'L',
  'ļ' => 'l',
  'Ľ' => 'L',
  'ľ' => 'l',
  'Ŀ' => 'L',
  'ŀ' => 'l',
  'Ł' => 'L',
  'ł' => 'l',
  'Ń' => 'N',
  'ń' => 'n',
  'Ņ' => 'N',
  'ņ' => 'n',
  'Ň' => 'N',
  'ň' => 'n',
  'ʼn' => "'n",
  'Ŋ' => 'NG',
  'ŋ' => 'ng',
  'Ō' => 'O',
  'ō' => 'o',
  'Ŏ' => 'O',
  'ŏ' => 'o',
  'Ő' => 'O',
  'ő' => 'o',
  'Œ' => 'OE',
  'œ' => 'oe',
  'Ŕ' => 'R',
  'ŕ' => 'r',
  'Ŗ' => 'R',
  'ŗ' => 'r',
  'Ř' => 'R',
  'ř' => 'r',
  'Ś' => 'S',
  'ś' => 's',
  'Ŝ' => 'S',
  'ŝ' => 's',
  'Ş' => 'S',
  'ş' => 's',
  'Š' => 'S',
  'š' => 's',
  'Ţ' => 'T',
  'ţ' => 't',
  'Ť' => 'T',
  'ť' => 't',
  'Ŧ' => 'T',
  'ŧ' => 't',
  'Ũ' => 'U',
  'ũ' => 'u',
  'Ū' => 'U',
  'ū' => 'u',
  'Ŭ' => 'U',
  'ŭ' => 'u',
  'Ů' => 'U',
  'ů' => 'u',
  'Ű' => 'U',
  'ű' => 'u',
  'Ų' => 'U',
  'ų' => 'u',
  'Ŵ' => 'W',
  'ŵ' => 'w',
  'Ŷ' => 'Y',
  'ŷ' => 'y',
  'Ź' => 'Z',
  'ź' => 'z',
  'Ż' => 'Z',
  'ż' => 'z',
  'ž' => 'z',
  'Ž' => 'Z',
}.freeze
MAC_EXCEPTIONS =
%w[
  MacEdo MacEvicius MacHado MacHar MacHin MacHlin MacIas MacIulis MacKie
  MacKle MacKlin MacKmin MacKmurdo MacQuarie MacLise MacKenzie
].freeze
FF_NAMES =
%w[Fforbes Fforde Ffinch Ffrench Ffoulkes].freeze
COMPOUND_NAMES =
[
  # Known families with a space in their surname
  'Baron Cohen',
  'Bonham Carter',
  'Holmes a Court',
  'Holmes à Court',
  'Lane Fox',
  'Lloyd Webber',
  'Pitt Rivers',
  'Sebag Montefiore',
  'Strang Steel',
  'Wedgwood Benn',
  'Wingfield Digby',
  # Sometimes companies appear as people
  'Corporation Company',
  'Corporation System',
  'Incorporations Limited',
  'Service Company',
].freeze
NAME_MODIFIERS =
[
  'Al',
  'Ap',
  'Ben',
  'D[aeiou]',
  'D[ao]s',
  'De[lrn]',
  'Dell[ae]',
  'El',
  'L[eo]',
  'La',
  'Of',
  'San',
  'St[\.]?',
  'V[ao]n',
  'Zur',
].freeze

Class Method Summary collapse

Class Method Details

.approximate_latin_chars(string) ⇒ Object

Any characters that resemble latin characters might usefully be transliterated into ones that are easy to type on an anglophone keyboard.



12
13
14
# File 'lib/name_tamer/strings/approximations.rb', line 12

def approximate_latin_chars(string)
  string.gsub(/[^\x00-\x7f]/u) { |char| APPROXIMATIONS[char] || char }
end

.downcase_after_apostrophe(string) ⇒ Object



11
12
13
# File 'lib/name_tamer/strings/capitalization.rb', line 11

def downcase_after_apostrophe(string)
  string.gsub(/'\w\b/, &:downcase) # Lowercase 's
end

.ensure_safe(string) ⇒ Object



58
59
60
# File 'lib/name_tamer/strings/core.rb', line 58

def ensure_safe(string)
  string.encode('UTF-8', invalid: :replace, undef: :replace, replace: '')
end

.ensure_space_after_initials(string) ⇒ Object



18
19
20
# File 'lib/name_tamer/strings/spacing.rb', line 18

def ensure_space_after_initials(string)
  string.gsub(/\b([a-z]\.)(?=[a-z0-9]{2,})/i) { "#{Regexp.last_match[1]} " }
end

.fix_apostrophe_modifiers(string) ⇒ Object



21
22
23
24
25
# File 'lib/name_tamer/strings/name_modifiers.rb', line 21

def fix_apostrophe_modifiers(string)
  %w[Dell D].reduce(string) do |name, modifier|
    name.gsub(/(.#{modifier}')(\w)/) { "#{Regexp.last_match[1].rstrip.downcase}#{Regexp.last_match[2]}" }
  end
end

.fix_encoding_errors(string) ⇒ Object

Strings that were wrongly encoded with single-byte encodings sometimes have tell-tale substrings that we can put back into the correct UTF-8 character



14
15
16
# File 'lib/name_tamer/strings/bad_encoding.rb', line 14

def fix_encoding_errors(string)
  string.gsub(BAD_ENCODING_PATTERNS) { |substring| BAD_ENCODING[substring] || substring }
end

.fix_ff(string) ⇒ Object

Fix ff wierdybonks



27
28
29
# File 'lib/name_tamer/strings/capitalization.rb', line 27

def fix_ff(string)
  FF_NAMES.reduce(string) { |name, ff_name| name.gsub(ff_name, ff_name.downcase) }
end

.fix_mac(string) ⇒ Object

Our list of terminal characters that indicate a non-celtic name used to include o but we removed it because of MacMurdo.



17
18
19
20
21
22
23
24
# File 'lib/name_tamer/strings/capitalization.rb', line 17

def fix_mac(string)
  return string unless /\bMac[A-Za-z]{2,}[^acizj]\b/.match?(string) || /\bMc/.match?(string)

  fixed = string.gsub(/\b(Ma?c)([A-Za-z]+)/) { Regexp.last_match[1] + Regexp.last_match[2].capitalize }

  # Fix Mac exceptions
  MAC_EXCEPTIONS.reduce(fixed) { |name, mac_name| name.gsub(/\b#{mac_name}/, mac_name.capitalize) }
end

.fix_name_modifiers(string) ⇒ Object

Fixes for name modifiers followed by space Also replaces spaces with non-breaking spaces Fixes for name modifiers followed by an apostrophe, e.g. d'Artagnan, Commedia dell'Arte



11
12
13
14
15
16
17
18
19
# File 'lib/name_tamer/strings/name_modifiers.rb', line 11

def fix_name_modifiers(string)
  fixed = NAME_MODIFIERS.reduce(string) do |name, modifier|
    name.gsub(/((?:[[:space:]]|^)#{modifier})([[:space:]]+|-)/) do
      "#{Regexp.last_match[1].rstrip.downcase}#{Regexp.last_match[2].tr(ASCII_SPACE, NONBREAKING_SPACE)}"
    end
  end

  fix_apostrophe_modifiers(fixed)
end

.fix_separators(string, separator) ⇒ Object

Make sure separators are not where they shouldn't be



30
31
32
33
34
35
36
37
# File 'lib/name_tamer/strings/core.rb', line 30

def fix_separators(string, separator)
  return string if separator.nil? || separator.empty?

  r = Regexp.escape(separator)

  # No more than one separator in a row, no leading or trailing separator
  string.gsub(/#{r}{2,}/, separator).gsub(/^#{r}|#{r}$/i, '')
end

.invalid_chars_to(string, separator) ⇒ Object

Change some characters embedded in words to our separator character e.g. example.com -> example-com



20
21
22
# File 'lib/name_tamer/strings/core.rb', line 20

def invalid_chars_to(string, separator)
  string.gsub(%r{(?<![[:space:]])[./](?![[:space:]])}, separator)
end

.nbsp_in_compound_name(string) ⇒ Object

Fix known last names that have spaces (not hyphens!)



8
9
10
11
12
# File 'lib/name_tamer/strings/compound_names.rb', line 8

def nbsp_in_compound_name(string)
  COMPOUND_NAMES.reduce(string) do |name, compound_name|
    name.gsub(compound_name, compound_name.tr(ASCII_SPACE, NONBREAKING_SPACE))
  end
end

.nbsp_in_name_modifier(string) ⇒ Object



27
28
29
30
31
# File 'lib/name_tamer/strings/name_modifiers.rb', line 27

def nbsp_in_name_modifier(string)
  NAME_MODIFIERS.reduce(string) do |name, modifier|
    name.gsub(/([[:space:]]#{modifier})([[:space:]])/i) { "#{Regexp.last_match[1]}#{NONBREAKING_SPACE}" }
  end
end

.presence(string) ⇒ Object



9
10
11
# File 'lib/name_tamer/strings/core.rb', line 9

def presence(string)
  string unless string.empty?
end

.remove_periods_from_initials(string) ⇒ Object



39
40
41
# File 'lib/name_tamer/strings/core.rb', line 39

def remove_periods_from_initials(string)
  string.gsub(/\b([a-z])\./i) { Regexp.last_match[1] }
end

.remove_spaces_from_initials(string) ⇒ Object



12
13
14
15
16
# File 'lib/name_tamer/strings/spacing.rb', line 12

def remove_spaces_from_initials(string)
  string.gsub(/\b([a-z])(\.)* \b(?![a-z0-9'À-ÿ]{2,})/i) do
    "#{Regexp.last_match[1]}#{Regexp.last_match[2]}"
  end
end

.safe_unescape(string) ⇒ Object

Unescape percent-encoded characters This might introduce UTF-8 invalid byte sequence so we take precautions



51
52
53
54
55
56
# File 'lib/name_tamer/strings/core.rb', line 51

def safe_unescape(string)
  unescaped = CGI.unescape(string.gsub('+', '%2B'))
  return string if string == unescaped

  ensure_safe(unescaped)
end

.space_around_comma(string) ⇒ Object

Ensure commas have exactly one space after them



8
9
10
# File 'lib/name_tamer/strings/spacing.rb', line 8

def space_around_comma(string)
  string.gsub(/[[:space:]]*,[[:space:]]*/, ', ')
end

.strip_unwanted(string, filter) ⇒ Object

Strip unwanted characters out completely



44
45
46
# File 'lib/name_tamer/strings/core.rb', line 44

def strip_unwanted(string, filter)
  string.gsub(filter, '')
end

.unescape_html(string) ⇒ Object

Remove HTML entities



25
26
27
# File 'lib/name_tamer/strings/core.rb', line 25

def unescape_html(string)
  CGI.unescapeHTML string
end

.upcase_first_letter(string) ⇒ Object



7
8
9
# File 'lib/name_tamer/strings/capitalization.rb', line 7

def upcase_first_letter(string)
  string.gsub(/\b\w/, &:upcase)
end

.upcase_initials(string) ⇒ Object

Upcase words with no vowels, e.g JPR Williams Except Ng http://en.wikipedia.org/wiki/Ng



33
34
35
36
37
# File 'lib/name_tamer/strings/capitalization.rb', line 33

def upcase_initials(string)
  string
    .gsub(/\b([bcdfghjklmnpqrstvwxz]+)\b/i) { Regexp.last_match[1].upcase }
    .gsub(/\b(NG)\b/i) { Regexp.last_match[1].capitalize }
end

.whitespace_to(string, separator) ⇒ Object

Change any whitespace into our separator character



14
15
16
# File 'lib/name_tamer/strings/core.rb', line 14

def whitespace_to(string, separator)
  string.gsub(/[[:space:]]+/, separator)
end