@@ -61,17 +61,18 @@ background (covered fully under :ref:`east-asian-names` in
6161:doc: `usage `): Chinese, Japanese, and Korean names put the family name
6262first in native script and are usually written with no space between
6363the parts. Both defaults follow from facts the script alone
64- establishes. A name written wholly in Han or Hangul is assigned
64+ establishes. A name written wholly in Han or Hangul — or one mixing
65+ kanji with kana, a combination only Japanese produces — is assigned
6566family-first, because every language written in those scripts orders
66- names that way — no language guess is involved. An unspaced hangul
67+ names that way; no language guess is involved. An unspaced hangul
6768name is additionally split into surname and given name, because hangul
6869is written by nothing but Korean and Korean surnames are a closed
6970census set that ships as default vocabulary. Splitting an unspaced
7071*Han * name is the one behavior the script cannot license — the same
7172characters could be a Chinese or a Japanese name, and a Chinese
7273surname list splits Japanese names in the wrong place — so it is not a
73- default: the ``zh `` pack below turns it on when you can declare the
74- data Chinese .
74+ default: the ``zh `` and `` ja `` packs below turn it on for data whose
75+ language you can declare .
7576
7677A pack is for something different: a *structural * rule, like reordering
7778a patronymic, that vocabulary alone can't express.
@@ -109,7 +110,7 @@ takes:
109110.. doctest ::
110111
111112 >>> locales.available()
112- ('ru', 'tr_az', 'zh')
113+ ('ja', ' ru', 'tr_az', 'zh')
113114 >>> locales.get(" ru" ) is locales.RU
114115 True
115116
@@ -123,6 +124,14 @@ parsing, equivalent to ``parser_for(locales.get("ru"))``.
123124
124125 * - Code
125126 - Turns on
127+ * - ``ja ``
128+ - Japanese segmentation — activates division for unspaced
129+ Japanese names, which needs a segmenter to act: install
130+ ``nameparser[ja] `` and pass
131+ ``parser_for(locales.JA, segmenter=locales.ja_segmenter()) ``
132+ (``山田太郎 `` → family ``山田 ``, given ``太郎 ``). The pack ships
133+ no surname list, because no list divides a kanji name, and no
134+ order: a Japanese name already reads family-first by default.
126135 * - ``ru ``
127136 - East Slavic patronymic order — detects a formal
128137 given/patronymic/family shape (Cyrillic and transliterated
@@ -139,9 +148,9 @@ parsing, equivalent to ``parser_for(locales.get("ru"))``.
139148 name order: native-script Han already reads family-first
140149 without a pack.
141150
142- ``ru `` and ``tr_az `` are policy-only — they carry no vocabulary of
143- their own. ``zh `` is both halves at once: a surname list, plus the one
144- policy field that turns segmentation on for the script it covers. See
151+ ``ja ``, `` ru `` and ``tr_az `` are policy-only — they carry no vocabulary
152+ of their own. ``zh `` is both halves at once: a surname list, plus the
153+ one policy field that turns segmentation on for the script it covers. See
145154:doc: `concepts ` for how that split (language vocabulary vs. behavior)
146155is drawn, and `Contributing a pack to nameparser `_ for which half a
147156new naming rule belongs in.
@@ -167,6 +176,47 @@ new naming rule belongs in.
167176 mixes traditions, parse the subsets separately with different
168177 parsers rather than enabling a pack over all of it.
169178
179+ .. _segmenter-contract :
180+
181+ Segmenters
182+ -----------
183+
184+ ``ja `` is policy-only in a second sense: it turns division on for
185+ Japanese text without supplying anything to divide with. That job goes
186+ to a **segmenter **, which is passed to
187+ :func: `~nameparser.parser_for ` rather than carried by the pack — a
188+ :class: `~nameparser.Locale ` is pure data, and a third-party callable is
189+ neither pure nor data. :func: `~nameparser.locales.ja_segmenter ` is the
190+ shipped one; writing your own is worth it for any script whose
191+ divisions you know better than a surname list does.
192+
193+ A :data: `~nameparser.Segmenter ` is any callable taking a token's text
194+ and returning a :class: `~nameparser.Segmentation ` — the interior
195+ offsets to cut at, plus how confident you are — or ``None `` to decline,
196+ leaving the token whole. Declining is the load-bearing half of the
197+ contract, because ``segment_scripts `` unions across packs: your
198+ segmenter is offered every token of every activated script, not only
199+ the ones its own pack turned on. Recognize the text you can actually
200+ read and return ``None `` for the rest, rather than answering for a
201+ script you never meant to handle. Exceptions are the one thing that
202+ does not stay inside the parse — a segmenter is your code, so its
203+ errors propagate out of ``parse() `` instead of being absorbed as
204+ content errors.
205+
206+ The pack half of that arrangement carries no words at all, which makes
207+ it the shortest kind of pack there is:
208+
209+ .. doctest ::
210+
211+ >>> from nameparser import Lexicon, Locale, PolicyPatch, Script
212+ >>> mine = Locale(code = " myscript" , lexicon = Lexicon.empty(),
213+ ... policy= PolicyPatch(
214+ ... segment_scripts= frozenset ({Script.HAN })))
215+
216+ ``nameparser/locales/ja.py `` is the shipped example of exactly that
217+ shape: activation is the pack's entire contribution, and a pack
218+ applied without a segmenter simply divides nothing.
219+
170220Creating your own Locale
171221-------------------------
172222
@@ -237,11 +287,14 @@ by ``tests/v2/test_locales.py``:
237287 needs at least one name exercising every alternation branch of every
238288 regex it defines — ``test_rotators_cover_every_marker_branch `` fails
239289 until each branch is hit. A pack declaring by *codepoint range *
240- (``zh ``) has no branches to sweep and drops out of that test, so its
241- rotators have to carry the same weight by hand: the unspaced names
242- the pack must split, one per shape of the vocabulary it ships —
243- single surname, compound surname, and any spelling variant it means
244- to cover.
290+ (``zh ``, ``ja ``) has no branches to sweep and drops out of that
291+ test, so its rotators have to carry the same weight by hand: the
292+ unspaced names the pack must split, one per shape of the vocabulary
293+ it ships — single surname, compound surname, and any spelling
294+ variant it means to cover. A pack that ships no vocabulary lists
295+ the shapes its *segmenter * must divide instead, and marks the
296+ rotator tests to skip when the optional dependency is absent, so
297+ the contract tests still run everywhere.
245298#. Keep the non-interference gate green over the shared corpus plus
246299 your rotators: every name the packed parser parses differently from
247300 the default must be one your ``DEVIATES `` predicate flags — no
@@ -256,16 +309,17 @@ by ``tests/v2/test_locales.py``:
256309 language its script does not * — a Chinese surname list, which
257310 silently mangles the Japanese names written in the same characters
258311 — belongs in the pack, where asking for it is the declaration.
259- ``ru `` and ``tr_az `` need no vocabulary at all and ship an empty
260- :class: `~nameparser.Lexicon `; ``nameparser/locales/zh.py `` is the
261- template for one that does.
312+ ``ja ``, `` ru `` and ``tr_az `` need no vocabulary at all and ship an
313+ empty :class: `~nameparser.Lexicon `; ``nameparser/locales/zh.py `` is
314+ the template for one that does.
262315#. Curate vocabulary conservatively, the same rule as
263316 :doc: `customize `: when you're unsure whether a word or a marker
264317 belongs, leave it out.
265318
266319``nameparser/locales/ru.py `` is the reference implementation for a
267320policy-only pack, ``nameparser/locales/zh.py `` for one that carries
268- vocabulary. Packs still in progress are tracked in issues `#272
269- <https://github.com/derek73/python-nameparser/issues/272> `_ (Japanese)
270- and `#146 <https://github.com/derek73/python-nameparser/issues/146 >`_
321+ vocabulary, and ``nameparser/locales/ja.py `` for one whose whole
322+ contribution is turning a stage on. Packs still in progress are
323+ tracked in issue `#146
324+ <https://github.com/derek73/python-nameparser/issues/146> `_
271325(Vietnamese).
0 commit comments