Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
4f5b956
Add the glued-honorific tail vocabulary (#308)
derek73 Jul 31, 2026
2b0f007
Vet the glued tail set against real surname data (#308)
derek73 Jul 31, 2026
b9c58a2
Add Lexicon.honorific_tails, the glued-peel vocabulary (#308)
derek73 Jul 31, 2026
b72150a
Enforce the honorific-tail subset invariant (#308)
derek73 Jul 31, 2026
9075fde
Peel a glued CJK honorific off the last name token (#308)
derek73 Jul 31, 2026
17f9c39
Never treat a post-nominal as a name site (#308)
derek73 Jul 31, 2026
be97aec
Decline rather than scan past a post-nominal surname site (#308)
derek73 Jul 31, 2026
90be6ba
Pin the suffix-comma peel and name the maiden reach (#308)
derek73 Jul 31, 2026
59aac1f
Let a recognized honorific reach the segmenter, not block it (#308)
derek73 Jul 31, 2026
f71a2e4
Pin that an honorific does not excuse a real boundary (#308)
derek73 Jul 31, 2026
d56fff2
Classify the glued-honorific diffs in the differential (#308)
derek73 Jul 31, 2026
aba6c03
Document the glued honorific peel (#308)
derek73 Jul 31, 2026
1d46fc6
Say what the 君 row actually pins (#308)
derek73 Jul 31, 2026
73fb92f
Finish the #308 staleness sweep (#308)
derek73 Jul 31, 2026
fdd3527
Scope the doctrine paragraph's count to what it counts (#308)
derek73 Jul 31, 2026
227426d
Note the lone-honorific fix and the peel's off-switch (#308)
derek73 Jul 31, 2026
063fd1a
Exempt only the tail the peel manufactured (#308)
derek73 Jul 31, 2026
931ce0e
Ship 박사님, the missing third -님 honorific (#308)
derek73 Jul 31, 2026
cd626cb
Correct six claims the code makes about itself (#308)
derek73 Jul 31, 2026
a527bd6
Pin the v1 shim's honorific_tails translation (#308)
derek73 Jul 31, 2026
288fef2
Say what the peel actually does, in prose (#308)
derek73 Jul 31, 2026
acbfefc
Pin the peel's segment choice and its two boundaries (#308)
derek73 Jul 31, 2026
3e5dad6
Reach the peel from a drawn lexicon (#308)
derek73 Jul 31, 2026
b31f759
Replace the peel docstring's self-refuting no-peel-twice example (#308)
derek73 Jul 31, 2026
dde0c60
Reclassify the FAMILY-comma honorific row to comma-family (#308)
derek73 Jul 31, 2026
d6cd806
Justify the segmenter's spaced-honorific rule by its cost (#308)
derek73 Jul 31, 2026
7819a3d
Scope the spaced-honorific advice to the segmenter path (#308)
derek73 Jul 31, 2026
2189401
Pin _is_post_nominal's strict suffix test with the input it names (#308)
derek73 Jul 31, 2026
b8a9252
Give the spaced-honorific test its missing control (#308)
derek73 Jul 31, 2026
e6a6b69
Correct five accuracy nits around the peel (#308)
derek73 Jul 31, 2026
41588b4
Let _split tag the tail it just built (#308)
derek73 Jul 31, 2026
2c5edcc
Derive the peel test lexicon from the stage's own (#308)
derek73 Jul 31, 2026
6ae3351
Drop a test whose assertion cannot fail (#308)
derek73 Jul 31, 2026
3171267
Pin the 殿 exclusion, the one nothing held (#308)
derek73 Jul 31, 2026
46fdc3e
Put a non-ASCII shape in the scaling benchmark (#308)
derek73 Jul 31, 2026
53611b1
Name the escape hatch that actually works (#308)
derek73 Jul 31, 2026
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
8 changes: 4 additions & 4 deletions AGENTS.md

Large diffs are not rendered by default.

34 changes: 26 additions & 8 deletions docs/customize.rst
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,15 @@ Removing works the same way, and drops the word from recognition:

A few fields mark a subset of another — ``given_name_titles`` over
``titles``, ``particles_ambiguous`` over ``particles``,
``suffix_acronyms_ambiguous`` over ``suffix_acronyms``. Entries must
appear in the base field too, so add to both and remove from the marker
first; anything else raises ``ValueError`` naming the orphans rather
than leaving a marker entry that no rule will ever consult.
``suffix_acronyms_ambiguous`` over ``suffix_acronyms``, and
``honorific_tails`` over ``suffix_words``. Entries belong in the base
field too, so add to both and remove from the marker first. The last
three enforce that: anything else raises ``ValueError`` naming the
orphans rather than leaving a marker entry that no rule will ever
consult. ``given_name_titles`` is deliberately unchecked — a title run
is matched as one space-joined string, so a legitimate entry like
``"sir and dame"`` is no single word in ``titles`` — and an orphan
there is inert rather than harmful.

Turning title detection off
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Expand Down Expand Up @@ -352,10 +357,23 @@ deactivates every script at once, which also stops a parser consulting
whatever segmenter it was given. The segmenter has an off-switch as
well — ``Parser(segmenter=None)``, which is the default; see
:ref:`segmenter-contract` for what one is expected to do with text it
does not handle. One Japanese behavior is not a policy field at all:
the katakana middle dot ・ separates tokens the way a space does,
decided in tokenization, so it applies however these two fields are
set.
does not handle. Two behaviors are not policy fields at all, and apply
however these two fields are set. The katakana middle dot ・ separates
tokens the way a space does, decided in tokenization. And a listed CJK
honorific glued to the end of a name token is split off it — ``田中さん``
reads family ``田中`` with ``さん`` in ``suffix`` — because the tail
vocabulary carries its own license rather than borrowing a script's:
every entry is a word that can never end a name, so there is no
per-script trust question for ``segment_scripts`` to answer. That
vocabulary is also the peel's off-switch —
``Lexicon.default().remove(honorific_tails={"さん"})`` leaves
``田中さん`` unsplit while the spaced ``田中 さん`` still reads ``さん``
as a suffix. Dropping a word from a marker field alone orphans
nothing, so that one needs no matching ``suffix_words`` edit. Emptying
the field is also the way to opt out of what the peel *costs* a
non-ASCII parse — an empty ``honorific_tails`` stops it at its first
guard, whereas ``segment_scripts`` never gated it and so cannot turn it
off.

.. note::

Expand Down
4 changes: 3 additions & 1 deletion docs/release_log.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ Release Log

- Add the Chinese locale pack ``locales.ZH`` — opt-in Han segmentation for unspaced names like ``毛泽东``, with the surname vocabulary it needs. A pack rather than a default because a Chinese surname list corrupts the Japanese names written in the same characters (``高橋一郎`` would split ``高`` + ``橋一郎``); Japanese data goes through ``locales.JA`` and its segmenter instead. It sets no name order, since native-script Han already reads family-first without it (#271)
- Add ``Lexicon.surnames``, ``Policy.script_orders``, ``Policy.segment_scripts``, the ``Script`` enum and the ``DEFAULT_SCRIPT_ORDERS`` constant to the public API. This is the first behavior nameparser keys on the script a name is written in; it is allowed only where the script itself settles a convention, never as a proxy for guessing the language (#271)
- Add ``Lexicon.honorific_tails``, the vocabulary the glued-honorific peel matches: entries that may be split off the END of a name token, matched longest-first. Deliberately a separate, narrower set than the spaced honorific vocabulary — a glued tail has no token boundary to lean on — and every entry is also a ``suffix_words`` entry, since the peeled piece is claimed by ordinary suffix classification. That subset rule is enforced, so extend both fields in the one call — ``Lexicon.default().add(suffix_words={"ちゃま"}, honorific_tails={"ちゃま"})``; adding to ``honorific_tails`` alone raises ``ValueError`` naming the orphan (#308)
- Add ``AmbiguityKind.SEGMENTATION``, reported when a surname split had a vocabulary-supported alternative: ``"남궁민수"`` is 남궁 + 민수 by the compound surname but 남 + 궁민수 by the single-syllable one, and longest-match had to pick. A name with only one possible split decided nothing and reports nothing (#271)
- Add the Japanese locale pack ``locales.JA`` and the segmenter factory ``locales.ja_segmenter()``, which together divide an unspaced Japanese name: ``parser_for(locales.JA, segmenter=locales.ja_segmenter())`` reads ``山田太郎`` as family ``山田``, given ``太郎``. The two halves are separate because no surname list can do this job — family and given names draw on the same kanji and the reading, not the spelling, decides most divisions — so the pack activates the stage and a third-party divider performs it. ``ja_segmenter()`` wraps `namedivider-python <https://pypi.org/project/namedivider-python/>`_, installed with the new ``nameparser[ja]`` extra; the core stays dependency-free, and ``ja_segmenter(gbdt=True)`` selects namedivider's more accurate gradient-boosted model, which downloads its data on first use. With both packs registered, ``locales.available()`` is now ``('ja', 'ru', 'tr_az', 'zh')`` (closes #272)
- Add ``Segmentation`` and the ``Segmenter`` type alias to the public API, plus the keyword-only ``Parser(segmenter=...)`` hook they describe: any callable from a token's text to a ``Segmentation`` (the interior offsets to cut at, and a confidence) or ``None`` to decline. It is consulted only for scripts listed in ``Policy.segment_scripts``, and only where the surname vocabulary declined first, so ``parser_for(locales.ZH, locales.JA, segmenter=...)`` composes — a listed Chinese surname wins, the segmenter takes the rest. Read that composition with the zh pack's own warning still attached: vocabulary-first means a Japanese kanji name opening on a listed Chinese surname never reaches the segmenter, so ``高橋一郎`` still splits ``高`` + ``橋一郎`` under the stack exactly as it does under ``locales.ZH`` alone. The two packs are alternatives, one per corpus; stack them only for genuinely mixed data that accepts that trade (#272)
Expand All @@ -25,7 +26,8 @@ Release Log
- Fix names containing 〆 (U+3006, the shime mark that opens Japanese surnames like 〆木 and 〆谷) parsing given-first: the script classifier now counts 〆 as Han, extending the 々 entry the table already carries, and going a step further than it — 々 is Script=Han and merely outside the ideograph blocks, while 〆 is Script=Common — so these names take the East Asian family-first reading like any other wholly-Han name. ``Policy(script_orders={})`` restores the positional reading for these names exactly as for other wholly-Han names (#303)
- Fix the katakana middle dot ``・`` (U+30FB, and its halfwidth twin U+FF65) being read as part of a name rather than as the divider it is: it now separates tokens exactly as a space does. A transcribed foreign name therefore divides into its parts and, being wholly katakana, keeps its source order — ``"マイケル・ジャクソン"`` gives given ``マイケル``, family ``ジャクソン``, where 1.x left the whole string in ``first`` — while a kanji pair written the same way takes the family-first rule (``"高橋・一郎"`` → family ``高橋``). Native Japanese names never contain this character and no other script's names use it, so the separation is unconditional, which also means it is **not** covered by the two policy opt-outs. Rendering follows, the way the Korean split's does: the dot comes back as a space, so ``str(HumanName("マイケル・ジャクソン"))`` is now ``"マイケル ジャクソン"``, and that reaches delimited content too — the nickname in ``"山田 太郎 (マイケル・ジャクソン)"`` renders ``"マイケル ジャクソン"``. The Chinese interpunct ``·`` is not an unconditional separator like these two: U+00B7 is also the Catalan punt volat and appears inside legitimate names (``Gal·la``), so it divides only between classified-script characters — the transcription treatment it marks is #298's (#272)
- Fix 间隔号-divided transcriptions parsing as one unsplit token: U+00B7 — the interpunct Chinese text divides a transcribed foreign name with, ``威廉·莎士比亚`` for William Shakespeare — is now a token separator between characters of a classified script, and a name it divides keeps its source order and is never segmented: the dot is the transcription marker, playing the role pure katakana plays in the kana license. It divides ONLY between classified-script characters, so the Catalan punt volat interior to names like ``Gal·la`` is untouched. The Japanese nakaguro is deliberately not a transcription marker — ``高橋・一郎`` is roster formatting, 姓・名, and keeps its family-first reading — so a transcription typed with the wrong dot (``威廉・莎士比亚``) reads by the convention of the codepoint it was typed with; like the spaced form, only the Chinese dot rescues it. Rendering stays space-joined (``str(HumanName("威廉·莎士比亚"))`` is ``"威廉 莎士比亚"``); a custom ``string_format`` such as ``"{first}·{last}"`` reinstates the dot (#298)
- Fix spaced CJK postnominal honorifics parsing as name parts: 씨, 박사, 선생님, 교수님, 군, 양 (Korean — standardly written as their own token), 先生, 女士, 小姐, 博士, 教授 (Chinese, with 先生/博士/教授 shared with Japanese), and 様, 氏 (Japanese) now route to ``suffix``, so ``王小明 先生`` reads family ``王小明`` where the family-first default had confidently made 先生 the given name. Whole-token matching — which also reaches a glued surname+honorific token, since segmentation splits off the surname first (``김씨`` reads family 김, suffix 씨; ``王先生`` the same under ``locales.ZH``); a glued honorific after a given name (``김민준씨``) and glued kana (``山田太郎様``) stay out of reach, tracked as #308 (closes #307)
- Fix spaced CJK postnominal honorifics parsing as name parts: 씨, 박사, 선생님, 교수님, 군, 양 (Korean — standardly written as their own token), 先生, 女士, 小姐, 博士, 教授 (Chinese, with 先生/博士/教授 shared with Japanese), and 様, 氏 (Japanese) now route to ``suffix``, so ``王小明 先生`` reads family ``王小明`` where the family-first default had confidently made 先生 the given name. Whole-token matching, which also reaches a glued surname+honorific token, since segmentation splits off the surname first (``김씨`` reads family 김, suffix 씨). The glued forms whole-token matching cannot reach are handled by the peel described below (closes #307)
- Fix glued CJK honorifics parsing as part of the name: an honorific written against the name — ``田中さん``, ``山田太郎様``, ``김민준씨``, ``김민준님``, ``王小明先生`` — is now split off the end of the last name token and routed to ``suffix``, where it had been swallowed by the name (the whole of ``田中さん`` was the family name; ``김민준씨`` gave given 민준씨). The peeled name then goes through the ordinary machinery, so the Korean split still happens (``김민준씨`` → family 김, given 민준, suffix 씨) and the ``田中さん`` case stops misreading as a kana-licensed composite. Only entries that can never END a name peel — 씨, 님, 박사, 박사님, 선생님, 교수님, さん, さま, くん, ちゃん, 様, 先生, 教授, 女士, 小姐 — while 양, 군, 氏, 博士 and 殿 are recognized in their spaced form only, because 김지양 is a given name, 田中博士 is Tanaka Hiroshi as readily as Doctor Tanaka, and some ninety Japanese surnames end in 殿 (鵜殿, 真殿); 君 is recognized in neither form, since 王君 is a complete Chinese name, though its kana spelling くん peels. Seven entries are new vocabulary in this change (さん, さま, くん, ちゃん, 殿, 님, 박사님), so their spaced forms route to ``suffix`` too (``田中 さん``, ``田中 殿``, ``김민준 님``, ``김민준 박사님``). 박사님 closes a gap in the shipped set rather than opening new ground: 선생님 and 교수님 shipped in 2.1 without it, so ``김민준박사님`` stranded 박사 in the given name and the spaced ``김민준 박사님`` came back as two suffixes for one honorific. Exactly one honorific peels off a token, and every entry is a whole honorific rather than a part of one. A token that is nothing but an honorific is no longer taken apart either — ``선생님`` and ``박사`` now stay whole where hangul segmentation had split them 선 + 생님 and 박 + 사, since 선 and 박 are listed surnames — which is **default-on** in its own right, hangul segmentation being a default. A configured segmenter benefits twice over: it is handed the name without the honorific, and the honorific the peel just cut off does not then look to it like a boundary the writer drew — so ``parser_for(locales.JA, segmenter=ja_segmenter())`` reads ``山田太郎様`` as family 山田, given 太郎, suffix 様. Worth knowing before you upgrade: that exemption is what makes a GLUED honorific stop protecting a name from division, so a family name written alone with one — ``田中さん`` — now divides the way bare ``田中`` already did (family 田, given 中, suffix さん). It is exactly and only the peeled tail that is exempt. A SPACED honorific is a token boundary its writer typed, and anything standing beside a name calls the segmenter off, so the name is left as written: ``田中 さん`` and ``佐藤 氏`` keep family 田中 and 佐藤 under the pack — the division the pack gives them without this change, whichever field the honorific itself lands in. That is the conservative reading rather than a claim about intent: a spaced honorific cannot be told apart from a spaced given name by position, and counting it as one keeps four real surnames whole (``佐藤 氏``, ``田中 様``, ``鈴木 先生``, ``中村 教授``) at the price of the one division it then declines to make (``山田太郎 様``). Writing the honorific spaced is therefore an opt-out in its own right on the SEGMENTER path, alongside declining the pack or the segmenter. It is no lever where the VOCABULARY divides the name, the two spellings agreeing exactly there — ``김민준 씨`` and ``김민준씨`` both give family 김, given 민준, suffix 씨, as do ``王小明 先生`` and ``王小明先生`` under the Chinese pack — and Korean data has no pack to decline either, hangul segmentation being on by default. **Default-on: changes parse output for glued CJK honorific forms**, through ``HumanName`` as well as the 2.0 API (closes #308)
- Fix NFD-decomposed input missing the East Asian defaults entirely: script classification now normalizes to NFC before deciding, so a Korean or Japanese name typed on macOS — where decomposed text is routine — gets the same order rule as its composed twin, which it silently did not before. Segmentation MATCHING deliberately stays raw, so an unspaced NFD hangul name is ordered correctly but not split, rather than being split in the wrong place. One gotcha worth stating plainly: parse output preserves the encoding it was given, so for NFD input ``name.family == "김"`` is ``False`` even though it is the same name — compare NFC-normalized text when comparing across encodings (#272)
- Fix the Ukrainian conjunction ``й`` not joining the pieces around it: it is the euphonic alternate of ``і``, the two chosen by the surrounding vowel and consonant rather than by meaning (``"Олесь і Олена"`` but ``"Марія й Петро"``), so real Ukrainian data carries both spellings and shipping only ``і`` recognized just one of them. ``"Олесь й Олена Коваленки"`` now gives given ``"Олесь й Олена"`` where the ``й`` previously landed in ``middle``. Same treatment as the ``и``/``і`` entries added in 2.0.0, single-letter carve-out included: the conjunction joins only once the name has enough pieces, and a punctuated initial still wins, so ``"Й. Сліпий"`` is unaffected. Raised in a comment on #267

Expand Down
Loading