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
10 changes: 8 additions & 2 deletions docs/design/decisions.md

Large diffs are not rendered by default.

76 changes: 61 additions & 15 deletions docs/design/rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ H2. Rationale: before a name, an abbreviation is almost always a
family name (C1), so no shape or vocabulary reading makes a
title there.
"Xyz. Smith, John" → family="Xyz. Smith"
history: decisions.md#H2 · interacts: C1 · implemented: nameparser/_pipeline/_assign.py
history: decisions.md#H2 · interacts: C1, P4 · implemented: nameparser/_pipeline/_assign.py, nameparser/_pipeline/_group.py

H3. Rationale: compound titles are written as a run of title words,
connectives included; a title word standing inside the name is
Expand Down Expand Up @@ -157,9 +157,12 @@ P2. Rationale: a particle is written as part of the surname it
precedes, and a title stands outside the name entirely.
A particle joins the words after it into one name part, the
join running until the next particle starts a group of its own,
a trailing suffix begins, a maiden marker takes the words after
it (M2), or the name ends. The final group reads as the family
name;
a trailing suffix begins — read as assign will read it (S2),
over the pieces the chain leaves: a trailing roman numeral, or a
bare acronym with words to spare, ends the chain as a suffix word
does — a maiden marker takes the
words after it (M2), or the name ends. The final group reads as
the family name;
earlier groups read by position. The chain begins wherever the
name begins, and a preceding title does not move that point.
Where P1's fold has claimed the opening, the fold decides the
Expand All @@ -172,12 +175,25 @@ P2. Rationale: a particle is written as part of the surname it
"Juan de" → family="de" · boundary
"de la Cruz Juan Carlos" family-first → family="de la Cruz"
"John van der Berg PhD" → family="van der Berg"
"John van der Berg V" → family="van der Berg"
"John van der Berg V" → suffix="V"
"John van der Berg Ma" → suffix="Ma"
"John van der J. V" → family="van der J. V" · boundary
"Freiherr von Berg MA" → family="von Berg MA"
"Freiherr von Richthofen V" → suffix="V" · boundary
"John van der Berg née Jones" → family="van der Berg"
Accepted: a particle of the unambiguous suffix vocabulary too
(vd, mc) is a suffix piece to the peel, so where it opens the
trailing run the chain stops before it as before any suffix
word, and the peel takes it; where it continues a prefix run,
the run takes it as a particle, as P6 reads it after a comma.
"John Smith Mc V" → suffix="Mc, V"
"John van Mc" → family="van Mc"
Accepted: a caller wanting the combined double-surname reading
(#132's ask) has it as the surnames view rather than the
family field.
"Vincent van Gogh van Beethoven" → surnames="van Gogh van Beethoven"
history: decisions.md#P2 · interacts: P1, P4, M2 · implemented: nameparser/_pipeline/_group.py, nameparser/_pipeline/_post_rules.py
history: decisions.md#P2 · interacts: P1, P4, M2, S2 · implemented: nameparser/_pipeline/_group.py, nameparser/_pipeline/_post_rules.py

P3. Rationale: connective words ("y", "of the") bind name words into
one name part; but a single letter in a short name is more
Expand Down Expand Up @@ -226,11 +242,14 @@ P4. Rationale: a particle links forward from inside a name; at the
comes from the fold (P1) or from position (O4), never from a
join. This is why a title before a leading particle changes
nothing (the title is not a name word), and why "Van Johnson"
is a given-name reading at all.
is a given-name reading at all. An unlisted abbreviation before
the particle is as transparent as a listed title, since assign
reads it as one (H2).
"Van Johnson" → given="Van"
"Sir de Mesnil" → pieces=[["Sir"], ["de"], ["Mesnil"]]
"Xyz. van Johnson" → given="van"
"John van der Berg" → pieces=[["John"], ["van", "der", "Berg"]] · boundary
history: decisions.md#P2 · interacts: P1, P5 · implemented: nameparser/_pipeline/_group.py
history: decisions.md#P2 · interacts: P1, P5, H2 · implemented: nameparser/_pipeline/_group.py

P5. Rationale: some given-name words are incomplete alone — "abdul"
is a bound form that the next word completes.
Expand Down Expand Up @@ -259,12 +278,18 @@ P5. Rationale: some given-name words are incomplete alone — "abdul"
ambiguous acronym is a name word wherever the peel does not take
it; a marker left as a word that a particle join (P2) has already
taken travels with that join. A title word standing in the name
is a name word (H3) and joins like one: the pair is a given name
whatever tag the word carried. What there is to spare is what
is a name word (H3) and joins like one, and so is a particle the
chain has not taken (P2) — unless it is of the unambiguous
suffix vocabulary too (vd, mc), which the join declines as the
suffix piece it is:
the pair is a given name whatever tag the word carried, and
after a family comma the join runs before the trailing
particle's attachment (P6) sees the name. What
there is to spare is what
assign will leave: the join is tried on the pieces as it would
leave them, assign's trailing peel (S2) is read over that, and
the name words it leaves are the words to spare — a trailing
roman numeral, or a bare acronym with words behind it, is no
roman numeral, or a bare acronym the peel takes, is no
word to spare. The join joins two name words into one and
changes no suffix reading: a word the peel reads as a suffix
unjoined must read so joined, or the join declines. After a
Expand Down Expand Up @@ -293,6 +318,8 @@ P5. Rationale: some given-name words are incomplete alone — "abdul"
"abdul Smith Ma" → suffix="Ma"
"abdul Smith Berg Ma" → family="Berg" · boundary
"abdul Sir Smith Berg" → given="abdul Sir"
"Berg, abdul van" → given="abdul van"
"Berg, abdul vd" → family="vd Berg"
"abdul Jr Smith Berg" → given="abdul"
"abdul Jr Smith Berg" → middle="Jr Smith"
"abdul Ph. D. Smith Berg" → suffix="Ph. D."
Expand All @@ -316,7 +343,7 @@ P5. Rationale: some given-name words are incomplete alone — "abdul"
"Sheik abdul salam" family-first → family="abdul salam"
"Sheik abdul salam" family-first → given=""
"Sheik abdul salam" family-first-given-last → family="abdul salam"
history: decisions.md#P5 · interacts: S2, M2, H1, P2, P4 · implemented: nameparser/_pipeline/_group.py, nameparser/_pipeline/_post_rules.py
history: decisions.md#P5 · interacts: S2, M2, H1, P2, P4, P6 · implemented: nameparser/_pipeline/_group.py, nameparser/_pipeline/_post_rules.py

P6. Rationale: a particle ending the name has nothing to link
forward to, so it is not doing a particle's work there. A
Expand Down Expand Up @@ -383,7 +410,13 @@ P6. Rationale: a particle ending the name has nothing to link
tracked with the other contested memberships. `do` sits in the
AMBIGUOUS acronym half and was already read as a name word
there, so the precedence decides nothing for it.
history: decisions.md#P6 · interacts: C1, P1, S2 · implemented: nameparser/_pipeline/_post_rules.py
Accepted: a bound given word ahead of the trailing particle takes
it as its pair first (P5), so the attachment never sees it —
unless the particle is of the unambiguous suffix vocabulary too
(vd, mc), which the join declines and the attachment then takes.
"Berg, abdul van" → given="abdul van"
"Berg, abdul vd" → family="vd Berg"
history: decisions.md#P6 · interacts: C1, P1, S2, P5 · implemented: nameparser/_pipeline/_post_rules.py

## Suffixes: generational & credentials (S)

Expand Down Expand Up @@ -500,8 +533,12 @@ M1. Rationale: an enclosure the caller has declared to mean maiden
M2. Rationale: a maiden marker announces that what follows it is the
former family name; the marker is an announcement, not a name.
A recognized maiden marker standing after at least one name
word takes the words after it — up to any trailing suffix — as
the maiden name, and the marker itself is dropped. A marker
word takes the words after it — up to any suffix word, or the
trailing roman numeral assign reads as the suffix (S2), both as
written and as the take would leave the name, the word before
the numeral being then the word before the marker — as the
maiden name, and
the marker itself is dropped. A marker
with nothing after it, or nothing before it, is just a word.
A marker taken this way also bounds a particle join arriving from
its left (P2), so the family name's particles stop at the marker
Expand All @@ -511,6 +548,11 @@ M2. Rationale: a maiden marker announces that what follows it is the
"Jane Smith née Jones" → maiden="Jones"
"Jane née Jones Smith" → maiden="Jones Smith"
"Jane Smith née Jones PhD" → suffix="PhD"
"John née Jones Smith V" → maiden="Jones Smith"
"John née Jones Smith V" → suffix="V"
"Jane Smith née V" → suffix="V"
"J. née Jones Smith V" → maiden="Jones Smith V" · boundary
"Jane née Jones J. V" → maiden="Jones J. V" · boundary
"Jones née" → family="née" · boundary
"née Jones" → family="Jones" · boundary
"Jane van der Berg née Jones" → maiden="Jones"
Expand All @@ -531,7 +573,11 @@ M2. Rationale: a maiden marker announces that what follows it is the
would have bound the two into one name word (P3); the connective
then builds a family name out of what is left.
"Jane née Jr y Jones" → maiden=""
history: decisions.md#M2 · interacts: P2, P3, P5, R2, M1 · implemented: nameparser/_pipeline/_group.py
Accepted: a bare acronym the peel would take with words to spare
is maiden text all the same — the count it needs includes the
very words the marker removes, so the reading is left to assign.
"John née Jones Smith Ma" → maiden="Jones Smith Ma"
history: decisions.md#M2 · interacts: P2, P3, P5, R2, M1, S2 · implemented: nameparser/_pipeline/_group.py

## Commas & structure (C)

Expand Down
2 changes: 2 additions & 0 deletions docs/release_log.rst
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ Release Log

- Fix a bound given name joining past a credential that the suffix rule then takes, leaving no family: ``"abdul Smith Jr Ma"`` read given ``abdul Smith`` with no family, where ``"John Smith Jr Ma"`` reads family ``Smith`` with suffix ``Jr, Ma``. The join's reserve had re-derived the suffix rule's trailing walk by hand and left out one of its two forks -- a bare acronym such as ``Ma`` is a credential only with words to spare -- so it counted the acronym as a name word, joined, and the suffix rule then peeled it. The two stages now share one walk: the reserve tries the join, runs the suffix rule's own peel over the pieces as the join would leave them, and joins only when two name words became one and nothing else changed. ``"abdul Smith Jr Ma"`` reads family ``Smith``, suffix ``Jr, Ma``; ``"abdul Smith Ma"`` reads family ``Smith``, suffix ``Ma`` rather than joining the pair and keeping the credential as the family -- both as 1.4.0 read them and as ``John`` reads in their place; ``"abdul Smith Berg Ma"`` keeps its join, and a title word standing after the bound word joins again as it did in 1.4.0 (``"Berg, abdul Sir"`` reads given ``abdul Sir``) without the pair ever being read as a title (``"abdul Sheikh and Ahmad Bakar"`` reads given ``abdul Sheikh and Ahmad``). No name of the differential corpora has the shapes; the ``Ma`` rules examples carry the fix, byte-identical to 1.4.0 and moving against 2.0.0 and 2.1.0, and the title-word shapes are pinned in tests (closes #425)

- Fix a particle chain and a maiden name taking a trailing generational numeral as a name word: ``"John van der Berg V"`` read family ``van der Berg V`` and ``"John née Jones Smith V"`` read maiden ``Jones Smith V``, where ``"John Smith V"`` reads suffix ``V``. Both stops asked "is this a suffix?" with the test that vetoes a bare ``V`` as an initial -- the same question the bound given-name join asked until #401 -- and now ask the suffix rule's own walk, over the name as they would leave it: family ``van der Berg``, maiden ``Jones Smith``, suffix ``V``, for ``I`` and ``X`` alike; ``"John van der J. V"`` and ``"J. née Jones Smith V"`` keep their readings, the word before the numeral being an initial as written or as left. The chain stops before a bare credential with words to spare too, as ``"John Smith Ma"`` does, where the suffix rule will still find them once the chain has run: ``"John van der Berg Ma"`` reads suffix ``Ma``, which is how 1.4.0 read it, while ``"Freiherr von Berg MA"`` keeps family ``von Berg MA``; a maiden name keeps such a credential, since the words the marker takes are the very words that made it one. A particle that is also suffix vocabulary now ends the chain where it opens the trailing run: ``"John Smith Mc V"`` reads suffix ``Mc, V``. The chain also no longer swallows the given name behind an unlisted abbreviation: ``"Xyz. van Johnson"`` and ``"Esq. van Gogh"`` read given ``van``, as ``"Dr. van Johnson"`` has since #367, the chain and the bound given-name join now asking the same test that reads the abbreviation as a title (closes #424)

- Fix a name opening with a particle that is *never* a given name being split at the particle under a family-first name order -- ``Policy(name_order=FAMILY_FIRST)`` and ``Policy(name_order=FAMILY_FIRST_GIVEN_LAST)`` alike, and identically: ``"de Mesnil"`` read as family ``de``, given ``Mesnil``, and ``"de la Vega"`` as family ``de``, given ``la Vega``. Each is now the whole surname, as it has always been in the default order. The rule enforcing it asked for the particle by the ``GIVEN`` role, which under a family-first order belongs to the token *after* the particle, so the test read the wrong word and declined. It now also asks by position -- the piece that opens the name -- so both shapes of the same rule are caught: where such a particle stands alone as a piece, either opening the name or in the given position, the name is left with no given name at all, the given and the middles folding into the family. Standing *alone* is the whole of it, and the rule claims nothing wider: ``"Juan de la Vega"`` under ``FAMILY_FIRST`` still reports given ``de la Vega``, because there the particle chained onto the words after it rather than standing alone, and a bare ``"de"`` with nothing to fold into is still reported as the given name. The decision behind the fix: a word that can never be a given name leaves ``name_order`` nothing to decide, so declaring family-first is not a reason to make ``de`` a surname on its own. A leading particle that *may* be a given name is genuinely order-dependent and is untouched -- ``"van Gogh"`` still reads as family ``van``, given ``Gogh`` under both family-first orders. This is also what gives ``Lexicon.particles_ambiguous`` an effect outside the default order: taking a word out of it now changes the parsed fields under a family-first order, where before it moved only the ambiguity report. Seven of the 751 differential corpus names move, the same seven under each family-first order; default-order output is byte-identical over all 751, at the 1.4.0, 2.0.0 and 2.1.0 differential baselines alike (closes #359)

- Fix a family name made only of particle words reporting no base on the 2.0 API, so the surname vanished from ``family_base`` and, on both APIs, from the initials: ``parse("Anh Do")`` gave family ``Do`` with ``family_base`` ``''`` and initials ``A.``, and under ``Policy(name_order=FAMILY_FIRST)`` ``"Del Toro"`` gave family ``Del`` the same way. The empty base was a 2.0 regression rather than a longstanding bug -- 1.4.0's own guard kept ``HumanName("Anh Do").last_base`` at ``Do``, and the facade has kept it right throughout; what 1.4.0 and 2.1 shared was the missing initial. A particle earns its name by joining forward to the word it modifies, so a particle standing alone in a name part is not doing a particle's work there and reads as an ordinary name word: it anchors the base, leaves the particles view, and contributes an initial. ``"Anh Do"`` is now base ``Do``, initials ``A. D.``; ``"Juan van der"`` is base ``van der``, initials ``J. v. d.``; ``"Nguyen, Van Le"`` initials ``V. L. N.`` where the middle name used to be dropped. Position decides this, not vocabulary -- whether the word is borne as a surname somewhere does not enter into it, which reverses the reasoning ``rules.md#R2`` carried before. The invariant it exists to hold: a non-empty family always has a non-empty base, because a particle needs a base to attach to. Where the particles DO join a name word nothing changes -- ``"Juan de la Vega"`` keeps base ``Vega``, particles ``de la`` and initials ``J. V.``. The parse fields themselves do not move: ``title``, ``given``, ``middle``, ``family``, ``suffix``, ``nickname`` and ``maiden`` are byte-identical over all 751 differential corpus names in all three name orders, and only the derived views and the initials change -- which is why the differential harness, which compares those seven roles, reports no diffs for this at any baseline. One consequence to know about: where the whole family is particles AND a tussenvoegsel was attached to it (#379), the two APIs order the initials differently -- ``parse("der, y van").initials()`` is ``y. d. v.`` against the facade's ``y. v. d.`` -- because ``initials()`` reads tokens in written order while the family FIELD renders folded words first. That affects 87 constructed inputs and no corpus name; it is a pre-existing ordering gap in ``initials()`` that this change makes visible, tracked at #408 (closes #385, closes #402)
Expand Down
Loading