Skip to content

Commit 76098cc

Browse files
authored
Merge pull request #205 from derek73/fix/bound-first-name-comma-branch
Fix missing bound-first-name join in suffix-comma branch
2 parents 9e11ca4 + 67621fe commit 76098cc

3 files changed

Lines changed: 38 additions & 1 deletion

File tree

AGENTS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ Parse flow:
9797
3. `parse_pieces()` — splits on spaces, detects dotted abbreviations like "Lt.Gov." and adds them to constants dynamically
9898
4. `join_on_conjunctions()` — merges pieces adjacent to conjunctions into single tokens (e.g. `['Secretary', 'of', 'State']``['Secretary of State']`); also joins prefix particles to the following lastname token
9999
— a piece merged with a title *or prefix* neighbor is re-registered into that constant set so later steps still recognize it, e.g. `von`+`und`+`zu` → registered as a prefix so the whole phrase joins to the last name (German "von und zu"; PR #191)
100-
4a. `_join_bound_first_name()` — called immediately after step 4 in both the no-comma and lastname-comma paths; merges bound given-name prefixes (e.g. "abdul") with the next piece before the assignment loop runs; suffixes are still in `pieces` at this point, so the `reserve_last` guard must count non-suffix pieces only
100+
4a. `_join_bound_first_name()` — called immediately after step 4 in all three paths that build a first-name-bearing token sequence: no-comma, lastname-comma (post-comma pieces), and suffix-comma (`parts[0]`); merges bound given-name prefixes (e.g. "abdul") with the next piece before the assignment loop runs; suffixes are still in `pieces` at this point, so the `reserve_last` guard must count non-suffix pieces only. Not called for the lastname portion itself (`lastname_pieces` in the lastname-comma path) — that token sequence is a surname, not first-name text, so the join must not apply there. The join lives at each of these three call sites individually rather than inside `parse_pieces()` itself. That's because `parse_pieces()` is also called for the lastname portion with the same `additional_parts_count` value used for the (joinable) post-comma given-names portion, so `additional_parts_count` alone can't disambiguate which callers want the join.
101101
5. Iterates pieces, assigning to `title_list`, `first_list`, `middle_list`, `last_list`, `suffix_list`
102102
6. `post_process()``handle_firstnames()` swaps first/last when only a title + one name; then, gated on `patronymic_name_order`, `handle_east_slavic_patronymic_name_order()` and `handle_turkic_patronymic_name_order()` reorder Russian-formal-order and reversed Turkic patronymics; then, gated on `middle_name_as_last`, `handle_middle_name_as_last()` folds `middle_list` into `last_list`; finally `handle_capitalization()` applies optional auto-cap. Any new `self._attr` used by `post_process()` helpers must be initialized in `__init__` (with its default value) — the direct-kwargs path bypasses `parse_full_name()`, so the attribute won't exist otherwise.
103103

nameparser/parser.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1072,6 +1072,7 @@ def parse_full_name(self) -> None:
10721072

10731073
self.suffix_list += parts[1:]
10741074
pieces = self.parse_pieces(parts[0].split(' '))
1075+
pieces = self._join_bound_first_name(pieces, reserve_last=True)
10751076
log.debug("pieces: %s", str(pieces))
10761077
for i, piece in enumerate(pieces):
10771078
try:

tests/test_bound_first_names.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,42 @@ def test_mid_name_prefix_becomes_last_prefix(self) -> None:
106106
self.m(hn.first, "ahmed", hn)
107107
self.m(hn.last, "abu bakr", hn)
108108

109+
# --- suffix-comma path ---
110+
def test_suffix_comma_join(self) -> None:
111+
hn = HumanName("Abdul Salam Hassan, MD")
112+
self.m(hn.first, "Abdul Salam", hn)
113+
self.m(hn.middle, "", hn)
114+
self.m(hn.last, "Hassan", hn)
115+
self.m(hn.suffix, "MD", hn)
116+
117+
def test_suffix_comma_join_with_middle(self) -> None:
118+
hn = HumanName("Abdul Salam Ahmed Salem, MD")
119+
self.m(hn.first, "Abdul Salam", hn)
120+
self.m(hn.middle, "Ahmed", hn)
121+
self.m(hn.last, "Salem", hn)
122+
self.m(hn.suffix, "MD", hn)
123+
124+
def test_suffix_comma_guard_two_tokens_no_join(self) -> None:
125+
"""Guard: only last name remains after prefix → no join, even with suffix comma."""
126+
hn = HumanName("Abdul Salam, MD")
127+
self.m(hn.first, "Abdul", hn)
128+
self.m(hn.last, "Salam", hn)
129+
self.m(hn.suffix, "MD", hn)
130+
131+
def test_suffix_comma_title_kept_prefix_joins(self) -> None:
132+
hn = HumanName("Dr. Abdul Salam Hassan, MD")
133+
self.m(hn.title, "Dr.", hn)
134+
self.m(hn.first, "Abdul Salam", hn)
135+
self.m(hn.last, "Hassan", hn)
136+
self.m(hn.suffix, "MD", hn)
137+
138+
def test_suffix_comma_abu_bakr_al_baghdadi(self) -> None:
139+
"""abu joins forward as first-prefix; al joins forward as last-prefix, even with suffix comma."""
140+
hn = HumanName("Abu Bakr Al Baghdadi, MD")
141+
self.m(hn.first, "Abu Bakr", hn)
142+
self.m(hn.last, "Al Baghdadi", hn)
143+
self.m(hn.suffix, "MD", hn)
144+
109145
# --- opt-out ---
110146
def test_opt_out_via_clear(self) -> None:
111147
"""Clearing bound_first_names restores prior behavior."""

0 commit comments

Comments
 (0)