Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions nameparser/config/prefixes.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,12 @@
#: correct parsing of the last name "von bergen wessels".
PREFIXES = set([
"'t",
'aan',
'aen',
'abu',
'af',
'al',
'auf',
'av',
'bar',
'bat',
Expand All @@ -30,25 +33,36 @@
'delle',
'delli',
'dello',
'dem',
'den',
'der',
'di',
'dí',
'do',
'dos',
'du',
'freiherr',
'freiherrin',
'heer',
'het',
'ibn',
'la',
'le',
'mac',
'mc',
'op',
'san',
'santa',
'st',
'ste',
'te',
'ter',
'tho',
'thoe',
'van',
'vande',
'vander',
'vd',
'vel',
'vom',
'von',
Expand Down
10 changes: 10 additions & 0 deletions nameparser/config/suffixes.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@
'asp',
'atc',
'awb',
'ba',
'bca',
'bcl',
'bcss',
Expand All @@ -107,6 +108,7 @@
'bpe',
'bpi',
'bpt',
'bsc',
'bt',
'btcs',
'bts',
Expand Down Expand Up @@ -507,8 +509,15 @@
'mcse',
'mct',
'md',
'mda',
'mdb',
'mdbb',
'mdep',
'mdhb',
'mdiv',
'mdl',
'mem',
'meng',
'mfa',
'micp',
'mieee',
Expand Down Expand Up @@ -653,6 +662,7 @@
'sphr',
'sra',
'sscp',
'stb',
'stmieee',
'tbr-ct',
'td',
Expand Down
9 changes: 9 additions & 0 deletions nameparser/config/titles.py
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,7 @@
'detective',
'developer',
'dhr',
'dipl.-ing',
'diplomat',
'dir',
'director',
Expand Down Expand Up @@ -282,6 +283,7 @@
'fadm',
'family',
'federal',
'fh-prof',
'field',
'film',
'financial',
Expand Down Expand Up @@ -312,6 +314,7 @@
'goodwife',
'governor',
'graf',
'gräfin',
'grand',
'großfürst',
'group',
Expand Down Expand Up @@ -412,6 +415,7 @@
'mcpoc',
'mcpon',
'md',
'me',
'member',
'memoirist',
'merchant',
Expand Down Expand Up @@ -470,6 +474,7 @@
'paleontologist',
'pastor',
'patriarch',
'pd',
'pediatrician',
'personality',
'petty',
Expand Down Expand Up @@ -512,6 +517,7 @@
'printer',
'printmaker',
'prinz',
'priv.-doz',
'prior',
'private',
'pro',
Expand All @@ -526,6 +532,7 @@
'pursuivant',
'pv2',
'pvt',
'ra',
'rabbi',
'radio',
'radm',
Expand Down Expand Up @@ -648,6 +655,7 @@
'tsgt',
'uk',
'united',
'univ.prof',
'us',
'vadm',
'vardapet',
Expand All @@ -665,6 +673,7 @@
'warrant',
'wing',
'wm',
'wp',
'wo-1',
'wo1',
'wo2',
Expand Down
10 changes: 10 additions & 0 deletions nameparser/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -1149,6 +1149,11 @@ def join_on_conjunctions(self, pieces: list[str], additional_parts_count: int =
if self.is_title(pieces[i+1]):
# when joining to a title, make new_piece a title too
self.C.titles.add(new_piece)
if self.is_prefix(pieces[i+1]):
# when joining to a prefix, make new_piece a prefix too, so
# e.g. "von" + "und" bridges into "von und" and can still
# chain onto a following prefix/lastname (see "von und zu")
self.C.prefixes.add(new_piece)
pieces[i] = new_piece
pieces.pop(i+1)
# subtract 1 from the index of all the remaining conjunctions
Expand All @@ -1161,6 +1166,11 @@ def join_on_conjunctions(self, pieces: list[str], additional_parts_count: int =
if self.is_title(pieces[i-1]):
# when joining to a title, make new_piece a title too
self.C.titles.add(new_piece)
if self.is_prefix(pieces[i-1]):
# when joining to a prefix, make new_piece a prefix too, so
# e.g. "von" + "und" bridges into "von und" and can still
# chain onto a following prefix/lastname (see "von und zu")
self.C.prefixes.add(new_piece)
pieces[i-1] = new_piece
pieces.pop(i)
rm_count = 2
Expand Down
51 changes: 51 additions & 0 deletions tests/test_conjunctions.py
Original file line number Diff line number Diff line change
Expand Up @@ -223,3 +223,54 @@ def test_conjunction_in_an_address_with_a_first_name_title(self) -> None:
def test_name_is_conjunctions(self) -> None:
hn = HumanName("e and e")
self.m(hn.first, "e and e", hn)

def test_conjunction_bridges_prefix_chain(self) -> None:
# "von" and "zu" are both prefixes, but "und" between them is only a
# conjunction. join_on_conjunctions() merges "von und zu" into one
# piece before the prefix-joining step runs, so without registering
# that merged piece as a prefix too, it's stranded in the middle name
# instead of joining to the last name. See German nobility styles
# like "von und zu".
hn = HumanName("Alois von und zu Liechtenstein")
self.m(hn.first, "Alois", hn)
self.m(hn.middle, "", hn)
self.m(hn.last, "von und zu Liechtenstein", hn)

def test_conjunction_bridges_prefix_chain_with_leading_title(self) -> None:
# Same bridging, but with extra prefix words on both sides of the
# conjunction and a leading title-like word ("Freiherrin") that is
# itself a prefix, confirming the chain still joins fully into last.
hn = HumanName("Annette Charlotte Freiherrin von und zu der Tann-Rathsamhausen")
self.m(hn.first, "Annette", hn)
self.m(hn.middle, "Charlotte", hn)
self.m(hn.last, "Freiherrin von und zu der Tann-Rathsamhausen", hn)

def test_conjunction_prefix_merge_at_start_stays_first_name(self) -> None:
# Guards the i == 0 branch of the same fix: when the conjunction is
# merged with a following prefix at the very start of the name, the
# existing leading-prefix rule (a lone prefix opening the name is
# treated as part of the first name, not joined to last) must still
# apply to the merged piece.
hn = HumanName("and van Buren")
self.m(hn.first, "and van", hn)
self.m(hn.last, "Buren", hn)

def test_conjunction_bridges_word_that_is_both_title_and_prefix(self) -> None:
# "freiherr" is registered as both a title and a prefix. When it sits
# next to a conjunction, join_on_conjunctions() runs the is_title and
# is_prefix checks independently (not elif), so the merged piece
# ("freiherr und") is added to both constants sets. Confirms that
# dual registration doesn't break the prefix-bridging into last.
hn = HumanName("Fritz Freiherr und von Bar")
self.m(hn.first, "Fritz", hn)
self.m(hn.middle, "", hn)
self.m(hn.last, "Freiherr und von Bar", hn)

def test_conjunction_bridges_prefix_chain_with_multiple_conjunctions(self) -> None:
# Two separate conjunctions ("und" appearing twice, not contiguous)
# each bridge their own pair of adjacent prefixes, so both merges
# must register as prefixes for the whole chain to join into last.
hn = HumanName("Alois von und zu und von Liechtenstein")
self.m(hn.first, "Alois", hn)
self.m(hn.middle, "", hn)
self.m(hn.last, "von und zu und von Liechtenstein", hn)