diff --git a/nameparser/parser.py b/nameparser/parser.py index a651cf6..f8dd297 100644 --- a/nameparser/parser.py +++ b/nameparser/parser.py @@ -487,6 +487,23 @@ def are_suffixes(self, pieces: Iterable[str]) -> bool: return False return True + def are_suffixes_after_comma(self, pieces: Iterable[str]) -> bool: + """Like are_suffixes, but pieces found in suffix_not_acronyms are + accepted unconditionally without passing through is_suffix(). + + Used when detecting suffix-comma format (e.g. "John Ingram, V") where + the post-comma position is unambiguous. This covers all + suffix_not_acronyms members (i, ii, iii, iv, v, jr, sr, etc.), + case-insensitively, including single-letter entries that is_suffix() + would otherwise reject via is_an_initial(). + """ + for piece in pieces: + if lc(piece) in self.C.suffix_not_acronyms: + continue + if not self.is_suffix(piece): + return False + return True + def is_rootname(self, piece: str) -> bool: """ Is not a known title, suffix or prefix. Just first, middle, last names. @@ -686,7 +703,7 @@ def parse_full_name(self) -> None: post_comma_pieces = self.parse_pieces(parts[1].split(' '), 1) - if self.are_suffixes(parts[1].split(' ')) \ + if self.are_suffixes_after_comma(parts[1].split(' ')) \ and len(parts[0].split(' ')) > 1: # suffix comma: diff --git a/tests/test_suffixes.py b/tests/test_suffixes.py index 115543d..221e803 100644 --- a/tests/test_suffixes.py +++ b/tests/test_suffixes.py @@ -34,6 +34,35 @@ def test_two_suffixes_lastname_comma_format(self) -> None: # NOTE: this adds a comma when the original format did not have one. self.m(hn.suffix, "Jr., MD", hn) + def test_roman_numeral_v_suffix_comma_format(self) -> None: + # suffix-comma position is unambiguous: 'V' must be a suffix, not a single-letter initial + hn = HumanName("John W. Ingram, V") + self.m(hn.first, "John", hn) + self.m(hn.middle, "W.", hn) + self.m(hn.last, "Ingram", hn) + self.m(hn.suffix, "V", hn) + + def test_roman_numeral_i_suffix_comma_format(self) -> None: + # 'I' has the same single-letter ambiguity as 'V' + hn = HumanName("John W. Smith, I") + self.m(hn.first, "John", hn) + self.m(hn.middle, "W.", hn) + self.m(hn.last, "Smith", hn) + self.m(hn.suffix, "I", hn) + + def test_suffix_not_acronym_then_acronym_suffix_comma_format(self) -> None: + # single-letter suffix_not_acronyms entry followed by an acronym suffix + hn = HumanName("John Smith, V MD") + self.m(hn.first, "John", hn) + self.m(hn.last, "Smith", hn) + self.m(hn.suffix, "V MD", hn) + + def test_two_suffix_not_acronyms_suffix_comma_format(self) -> None: + hn = HumanName("John Smith, V Jr.") + self.m(hn.first, "John", hn) + self.m(hn.last, "Smith", hn) + self.m(hn.suffix, "V Jr.", hn) + def test_two_suffixes_suffix_comma_format(self) -> None: hn = HumanName("Franklin Washington, Jr. MD") self.m(hn.first, "Franklin", hn)