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
11 changes: 11 additions & 0 deletions docs/design/decisions.md

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions docs/design/mechanisms.md

Large diffs are not rendered by default.

44 changes: 35 additions & 9 deletions docs/design/rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -1733,9 +1733,28 @@ R3. Rationale: initials abbreviate the person's name words; titles,
name gives "j. v.". Case repair IS handed a vocabulary, so it falls
back for the one question a word can answer on its own, and R4
says which. Revising the field through the parser classifies it
and matches the parse in both views. Stated without an example
line because every line here names an input string, and this
shape needs a field edited after the parse.
and matches the parse in both of the parsed name's views. Stated
without an example line because every line here names an input
string, and this shape needs a field edited after the parse.
The v1 facade's HumanName.initials() is a second view of this
question and IS handed a vocabulary: it reads the parse's reading
wherever a word is backed by a parsed token, and falls back
wherever a word is not — a field set as raw text, or a name
restored from a v1 pickle or copied through the same state hooks.
The connective question falls back to the same helper case repair
uses; the particle question was never asked of the parse in this
view at all — `_is_particle` is a live
vocabulary lookup for every word, backed or spliced alike — so a
family spliced to "de la vega" initials "j. v." on this view
against "j. d. l. v." on the parsed name's own. So the
two views agree on WHICH WORDS initial in a parsed name; what
still differs there is GROUPING, the facade initialing a joined
run as one element, which is where the name "Ph. D., John" gives
a run-together "J. P D." on this view against "J. P. D." on the
other. Stated in prose and not as example lines because both
shapes need a field edited after the parse, or a rendering the
other view does not have. decisions.md#R3 carries what all of it
costs and where it is pinned.
Accepted: the unsettled given-group answer above is neither rare
nor hypothetical — 26 of the corpus names carry a conjunction
among the given names (measured 2026-09-13; recompute by parsing
Expand Down Expand Up @@ -1802,12 +1821,19 @@ R4. Rationale: case repair is a display concern, applied only on
needs a reading on every word of the part, and a spliced field
has none on any, so that half falls through to particle treatment
and the "de la" boundary above stands. Initials are the contrast
worth knowing, and R3 states it: that view is handed no
vocabulary at all, so it falls back on neither question and a
spliced field's every word initials. revise() classifies the
value and crosses both questions, in both views: a middle revised
to "e-f" repairs to "E-F" as the parsed name does, where splicing
the same text in gives "e-F".
worth knowing, and R3 states it — but ask which initials view,
because the two answer oppositely. The parsed name's own view is
handed no vocabulary at all, so it falls back on neither question
and a spliced field's every word initials. The v1 facade's view IS
handed one: the connective question falls back to the helper this
rule uses, while the particle question is never asked of the
parse in this view at all — a live vocabulary lookup for every
word, backed or spliced alike — so a family spliced to "de la
vega" initials "j. v." there against the parsed view's "j. d. l.
v.". revise() classifies the value and crosses both questions, in
both of the parsed name's views: a middle revised to "e-f"
repairs to "E-F" as the parsed name does, where splicing the same
text in gives "e-F".
history: decisions.md#R4 · interacts: R2, R3, R5 · implemented: nameparser/_render.py

R5. Rationale: mixed case is evidence that the writer cased the name
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,7 +6,9 @@ Release Log

**Behavior Changes**

- **Fix a one-letter connective joining a name that gives no sign it is a connective.** ``HumanName("jose e maria santos")`` gives first ``jose``, middle ``e maria``, last ``santos``, where 1.4.0 through 2.3.0 gave first ``jose e maria``; and ``JUAN GARCIA Y LOPEZ`` gives last ``GARCIA Y LOPEZ``, where every release since 1.4.0 read the bare capital as an initial and gave middle ``GARCIA Y``. A single letter is an initial where the writing says so -- a bare Latin capital in a name that is not written wholly in one case -- and a name written wholly in one case says nothing either way, so the reading comes from the vocabulary there: ``e`` reads as an initial and ``y`` joins. Mixed-case input is untouched in both directions: ``Jose e Maria Santos`` still gives first ``Jose e Maria`` and ``Jose E Maria Santos`` still gives middle ``E Maria``. Short names move in the derived views rather than the fields, P3's three-word carve-out being unchanged: ``parse("john e smith").initials()`` is ``j. e. s.`` where 2.3.0 gave ``j. s.``, and ``HumanName("john e smith").capitalize()`` gives ``John E Smith`` where 2.3.0 gave ``John e Smith``; ``JUAN Y GARCIA`` moves the same way in reverse, ``parse(...).initials()`` giving ``J. G.`` where 2.3.0 gave ``J. Y. G.`` and ``capitalize()`` giving ``Juan y Garcia``. On those two short names ``HumanName.initials()`` is unchanged -- the facade reads the letter by vocabulary and written shape rather than by the parse, and a follow-up issue carries the split -- but where a ROLE moves the facade's initials follow the fields like any other view: ``HumanName("jose e maria santos").initials()`` is ``j. m. s.`` where 2.3.0 gave ``j. e. m. s.``. Seventeen names in the differential corpora are written in one case and carry a cased single-letter connective, and ten of them move something against 2.3.0. The Cyrillic reading is unchanged (``Хосе И Мария Сантос`` still gives first ``Хосе И Мария``), and Arabic ``و`` never enters the rule, having no case to be written against. A ``Lexicon`` knob decides which letters are marked, so the reading is configurable rather than fixed. See the ``P3`` entry of ``docs/design/decisions.md`` (closes #383, closes #479)
- **Fix a one-letter connective joining a name that gives no sign it is a connective.** ``HumanName("jose e maria santos")`` gives first ``jose``, middle ``e maria``, last ``santos``, where 1.4.0 through 2.3.0 gave first ``jose e maria``; and ``JUAN GARCIA Y LOPEZ`` gives last ``GARCIA Y LOPEZ``, where every release since 1.4.0 read the bare capital as an initial and gave middle ``GARCIA Y``. A single letter is an initial where the writing says so -- a bare Latin capital in a name that is not written wholly in one case -- and a name written wholly in one case says nothing either way, so the reading comes from the vocabulary there: ``e`` reads as an initial and ``y`` joins. Mixed-case input is untouched in both directions: ``Jose e Maria Santos`` still gives first ``Jose e Maria`` and ``Jose E Maria Santos`` still gives middle ``E Maria``. Short names move in the derived views rather than the fields, P3's three-word carve-out being unchanged: ``parse("john e smith").initials()`` is ``j. e. s.`` where 2.3.0 gave ``j. s.``, and ``HumanName("john e smith").capitalize()`` gives ``John E Smith`` where 2.3.0 gave ``John e Smith``; ``JUAN Y GARCIA`` moves the same way in reverse, ``parse(...).initials()`` giving ``J. G.`` where 2.3.0 gave ``J. Y. G.`` and ``capitalize()`` giving ``Juan y Garcia``. ``HumanName.initials()`` moves with them -- see the #528 bullet below, which closed a split this change opened and the same release closes. Seventeen names in the differential corpora are written in one case and carry a cased single-letter connective, and ten of them move something against 2.3.0. The Cyrillic reading is unchanged (``Хосе И Мария Сантос`` still gives first ``Хосе И Мария``), and Arabic ``و`` never enters the rule, having no case to be written against. A ``Lexicon`` knob decides which letters are marked, so the reading is configurable rather than fixed. See the ``P3`` entry of ``docs/design/decisions.md`` (closes #383, closes #479)

- **Fix HumanName.initials() reading a one-letter connective by vocabulary and written shape instead of by the parse.** ``HumanName("john e smith").initials()`` gives ``j. e. s.``, where every release from 1.4.0 through 2.3.0 gave ``j. s.``; ``JUAN Y GARCIA`` gives ``J. G.`` where 2.3.0 gave ``J. Y. G.``, and ``JUAN GARCIA Y LOPEZ`` gives ``J. G. L.`` where 2.3.0 gave ``J. G. Y. L.``. Those last two read 1.4.0's way at 2.3.0 and only there: 2.0.0 through 2.2.0 already gave today's answer, by the unrelated bug the 2.3.0 note below records as fixed (the facade dropping a bare capital that is also a one-letter conjunction, #462), so against those three releases neither name moves at all. The v1 facade decided whether a word was the connective by looking the word up and checking its shape, while ``parse(...).initials()`` read the tag the parse recorded -- so the change above, which reads a single letter in a one-case name from the vocabulary rather than from its case, moved one view and not the other. Both views of a parse now give the same answer. Mixed-case names are untouched on both, the writing having decided the letter: ``John E Smith`` is still ``J. E. S.`` and ``Scott E. Werner`` still ``S. E. W.``. So is a one-case name whose letter is outside the marked set -- ``maria y lopez`` is still ``m. l.``, ``y`` having joined before this release and after it. Two costs, and both match what ``capitalize()`` has always done: editing ``C.conjunctions`` after a name is parsed no longer changes its initials until ``full_name`` is assigned again, and a name restored from a pickle, copied with ``copy.copy``/``copy.deepcopy`` (the same state hooks), or built from keyword fields (``HumanName(first=..., middle=..., last=...)``) carries no tags, so its initials come from the vocabulary and can differ from a fresh parse of the same string. One private break, stated because a v1 subclass can hit it: an override of ``_process_initial`` written to v1's ``(name_part, firstname=False)`` signature now raises ``TypeError`` the first time ``initials()`` runs, since ``initials()`` passes the part's tokens. Such an override has to accept a ``tokens`` keyword *and pass it on* -- ``return super()._process_initial(name_part, firstname, tokens=tokens)`` -- to receive this fix. Widening the signature without forwarding still works, but on the pre-#528 STRING path: the token call hands the override the group's own text as ``name_part`` rather than an empty placeholder, so ``john e smith`` initials ``j. s.`` under such an override, not the ``j. e. s.`` above. A subclass overriding one of the public ``first_list``, ``middle_list`` or ``last_list`` properties keeps working too: that member takes the pre-2.4 vocabulary reading instead of the change above, while an un-overridden member still moves. See the ``R3`` entry of ``docs/design/decisions.md`` (closes #528)

**Additions**

Expand Down
12 changes: 8 additions & 4 deletions docs/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -910,10 +910,14 @@ no longer knows ``de la`` are particles, so ``family_particles``
empties and ``family_base`` takes the whole field.

A token the parse never saw carries no decision to honor, so a view
that is *handed* a vocabulary can fall back to it —
:meth:`~nameparser.ParsedName.capitalized` is the one that is, and it
falls back for one question only: whether a word is a conjunction or
an initial, which a word answers on its own. Whether a particle is
that is *handed* a vocabulary can fall back to it — of the parsed
name's own views, :meth:`~nameparser.ParsedName.capitalized` is the
one that is, and it falls back for one question only: whether a word
is a conjunction or an initial, which a word answers on its own.
(The v1 :class:`~nameparser.parser.HumanName` facade's ``initials()``
is the other view that is handed one, and takes the same fallback for
spliced text; it is not a method of the parsed name and is not what
this section describes.) Whether a particle is
acting as a particle is a fact about the whole part, and there is no
reading on any word of a spliced field to derive it from, so a family
set to ``de la`` stays lowercase where the same words parsed are
Expand Down
Loading