nameparser 2.4.2__tar.gz → 2.6.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {nameparser-2.4.2/nameparser.egg-info → nameparser-2.6.0}/PKG-INFO +9 -11
- {nameparser-2.4.2 → nameparser-2.6.0}/README.rst +7 -8
- {nameparser-2.4.2 → nameparser-2.6.0}/nameparser/_config_shim.py +12 -3
- {nameparser-2.4.2 → nameparser-2.6.0}/nameparser/_facade.py +9 -8
- {nameparser-2.4.2 → nameparser-2.6.0}/nameparser/_lexicon.py +40 -28
- {nameparser-2.4.2 → nameparser-2.6.0}/nameparser/_parser.py +8 -8
- {nameparser-2.4.2 → nameparser-2.6.0}/nameparser/_pipeline/_assemble.py +24 -5
- {nameparser-2.4.2 → nameparser-2.6.0}/nameparser/_pipeline/_assign.py +164 -76
- {nameparser-2.4.2 → nameparser-2.6.0}/nameparser/_pipeline/_classify.py +137 -34
- {nameparser-2.4.2 → nameparser-2.6.0}/nameparser/_pipeline/_comma.py +277 -76
- {nameparser-2.4.2 → nameparser-2.6.0}/nameparser/_pipeline/_extract.py +18 -15
- {nameparser-2.4.2 → nameparser-2.6.0}/nameparser/_pipeline/_group.py +141 -255
- {nameparser-2.4.2 → nameparser-2.6.0}/nameparser/_pipeline/_pieces.py +171 -105
- {nameparser-2.4.2 → nameparser-2.6.0}/nameparser/_pipeline/_post_rules.py +195 -134
- {nameparser-2.4.2 → nameparser-2.6.0}/nameparser/_pipeline/_script_segment.py +49 -105
- {nameparser-2.4.2 → nameparser-2.6.0}/nameparser/_pipeline/_segment.py +31 -14
- {nameparser-2.4.2 → nameparser-2.6.0}/nameparser/_pipeline/_state.py +11 -10
- {nameparser-2.4.2 → nameparser-2.6.0}/nameparser/_pipeline/_tokenize.py +32 -20
- {nameparser-2.4.2 → nameparser-2.6.0}/nameparser/_pipeline/_vocab.py +208 -67
- {nameparser-2.4.2 → nameparser-2.6.0}/nameparser/_policy.py +57 -20
- {nameparser-2.4.2 → nameparser-2.6.0}/nameparser/_render.py +142 -59
- {nameparser-2.4.2 → nameparser-2.6.0}/nameparser/_types.py +26 -15
- {nameparser-2.4.2 → nameparser-2.6.0}/nameparser/_version.py +4 -4
- {nameparser-2.4.2 → nameparser-2.6.0}/nameparser/config/__init__.py +3 -1
- nameparser-2.6.0/nameparser/config/_compiled.py +2159 -0
- nameparser-2.6.0/nameparser/config/_invariants.py +39 -0
- nameparser-2.6.0/nameparser/config/bound_given_names.py +27 -0
- nameparser-2.6.0/nameparser/config/capitalization.py +45 -0
- nameparser-2.6.0/nameparser/config/conjunctions.py +26 -0
- {nameparser-2.4.2 → nameparser-2.6.0}/nameparser/config/maiden_markers.py +10 -27
- nameparser-2.6.0/nameparser/config/particles.py +101 -0
- nameparser-2.6.0/nameparser/config/suffixes.py +144 -0
- nameparser-2.6.0/nameparser/config/titles.py +60 -0
- {nameparser-2.4.2 → nameparser-2.6.0}/nameparser/locales/ja.py +2 -2
- {nameparser-2.4.2 → nameparser-2.6.0}/nameparser/locales/zh.py +70 -6
- nameparser-2.6.0/nameparser/vocab/ak.toml +5 -0
- nameparser-2.6.0/nameparser/vocab/am.toml +9 -0
- nameparser-2.6.0/nameparser/vocab/ar.toml +526 -0
- nameparser-2.6.0/nameparser/vocab/as.toml +19 -0
- nameparser-2.6.0/nameparser/vocab/be.toml +23 -0
- nameparser-2.6.0/nameparser/vocab/bg.toml +61 -0
- nameparser-2.6.0/nameparser/vocab/bin.toml +9 -0
- nameparser-2.6.0/nameparser/vocab/bn.toml +182 -0
- nameparser-2.6.0/nameparser/vocab/bo.toml +13 -0
- nameparser-2.6.0/nameparser/vocab/bs.toml +29 -0
- nameparser-2.6.0/nameparser/vocab/ca.toml +8 -0
- nameparser-2.6.0/nameparser/vocab/cs.toml +51 -0
- nameparser-2.6.0/nameparser/vocab/da.toml +41 -0
- nameparser-2.6.0/nameparser/vocab/de.toml +207 -0
- nameparser-2.6.0/nameparser/vocab/el.toml +33 -0
- nameparser-2.6.0/nameparser/vocab/en.toml +4955 -0
- nameparser-2.6.0/nameparser/vocab/es.toml +101 -0
- nameparser-2.6.0/nameparser/vocab/et.toml +18 -0
- nameparser-2.6.0/nameparser/vocab/fa.toml +37 -0
- nameparser-2.6.0/nameparser/vocab/ff.toml +5 -0
- nameparser-2.6.0/nameparser/vocab/fi.toml +26 -0
- nameparser-2.6.0/nameparser/vocab/fil.toml +20 -0
- nameparser-2.6.0/nameparser/vocab/fr.toml +183 -0
- nameparser-2.6.0/nameparser/vocab/ga.toml +63 -0
- nameparser-2.6.0/nameparser/vocab/gd.toml +9 -0
- nameparser-2.6.0/nameparser/vocab/gu.toml +13 -0
- nameparser-2.6.0/nameparser/vocab/ha.toml +17 -0
- nameparser-2.6.0/nameparser/vocab/he.toml +133 -0
- nameparser-2.6.0/nameparser/vocab/hi.toml +211 -0
- nameparser-2.6.0/nameparser/vocab/hr.toml +41 -0
- nameparser-2.6.0/nameparser/vocab/hu.toml +17 -0
- nameparser-2.6.0/nameparser/vocab/hy.toml +23 -0
- nameparser-2.6.0/nameparser/vocab/id.toml +5 -0
- nameparser-2.6.0/nameparser/vocab/is.toml +25 -0
- nameparser-2.6.0/nameparser/vocab/it.toml +194 -0
- nameparser-2.6.0/nameparser/vocab/ja.toml +144 -0
- nameparser-2.6.0/nameparser/vocab/ka.toml +21 -0
- nameparser-2.6.0/nameparser/vocab/kk.toml +7 -0
- nameparser-2.6.0/nameparser/vocab/km.toml +11 -0
- nameparser-2.6.0/nameparser/vocab/kn.toml +5 -0
- nameparser-2.6.0/nameparser/vocab/ko.toml +88 -0
- nameparser-2.6.0/nameparser/vocab/kok.toml +7 -0
- nameparser-2.6.0/nameparser/vocab/lo.toml +5 -0
- nameparser-2.6.0/nameparser/vocab/lt.toml +35 -0
- nameparser-2.6.0/nameparser/vocab/lv.toml +17 -0
- nameparser-2.6.0/nameparser/vocab/mi.toml +5 -0
- nameparser-2.6.0/nameparser/vocab/mk.toml +15 -0
- nameparser-2.6.0/nameparser/vocab/ml.toml +9 -0
- nameparser-2.6.0/nameparser/vocab/mn.toml +11 -0
- nameparser-2.6.0/nameparser/vocab/mr.toml +91 -0
- nameparser-2.6.0/nameparser/vocab/ms.toml +52 -0
- nameparser-2.6.0/nameparser/vocab/mul.toml +58 -0
- nameparser-2.6.0/nameparser/vocab/my.toml +5 -0
- nameparser-2.6.0/nameparser/vocab/nb.toml +45 -0
- nameparser-2.6.0/nameparser/vocab/ne.toml +11 -0
- nameparser-2.6.0/nameparser/vocab/nl.toml +147 -0
- nameparser-2.6.0/nameparser/vocab/nn.toml +5 -0
- nameparser-2.6.0/nameparser/vocab/or.toml +9 -0
- nameparser-2.6.0/nameparser/vocab/pa.toml +29 -0
- nameparser-2.6.0/nameparser/vocab/pl.toml +24 -0
- nameparser-2.6.0/nameparser/vocab/pt.toml +70 -0
- nameparser-2.6.0/nameparser/vocab/ro.toml +17 -0
- nameparser-2.6.0/nameparser/vocab/ru.toml +88 -0
- nameparser-2.6.0/nameparser/vocab/sd.toml +21 -0
- nameparser-2.6.0/nameparser/vocab/shn.toml +5 -0
- nameparser-2.6.0/nameparser/vocab/si.toml +5 -0
- nameparser-2.6.0/nameparser/vocab/sk.toml +29 -0
- nameparser-2.6.0/nameparser/vocab/sl.toml +29 -0
- nameparser-2.6.0/nameparser/vocab/sr.toml +52 -0
- nameparser-2.6.0/nameparser/vocab/sv.toml +38 -0
- nameparser-2.6.0/nameparser/vocab/sw.toml +7 -0
- nameparser-2.6.0/nameparser/vocab/ta.toml +17 -0
- nameparser-2.6.0/nameparser/vocab/te.toml +9 -0
- nameparser-2.6.0/nameparser/vocab/th.toml +9 -0
- nameparser-2.6.0/nameparser/vocab/ti.toml +9 -0
- nameparser-2.6.0/nameparser/vocab/tr.toml +26 -0
- nameparser-2.6.0/nameparser/vocab/uk.toml +54 -0
- nameparser-2.6.0/nameparser/vocab/und.toml +21 -0
- nameparser-2.6.0/nameparser/vocab/ur.toml +33 -0
- nameparser-2.6.0/nameparser/vocab/vi.toml +9 -0
- nameparser-2.6.0/nameparser/vocab/yi.toml +5 -0
- nameparser-2.6.0/nameparser/vocab/yo.toml +15 -0
- nameparser-2.6.0/nameparser/vocab/zh.toml +153 -0
- nameparser-2.6.0/nameparser/vocab/zu.toml +6 -0
- {nameparser-2.4.2 → nameparser-2.6.0/nameparser.egg-info}/PKG-INFO +9 -11
- {nameparser-2.4.2 → nameparser-2.6.0}/nameparser.egg-info/SOURCES.txt +100 -0
- {nameparser-2.4.2 → nameparser-2.6.0}/pyproject.toml +16 -4
- {nameparser-2.4.2 → nameparser-2.6.0}/tests/conftest.py +28 -0
- {nameparser-2.4.2 → nameparser-2.6.0}/tests/test_capitalization.py +16 -16
- {nameparser-2.4.2 → nameparser-2.6.0}/tests/test_initials.py +4 -4
- {nameparser-2.4.2 → nameparser-2.6.0}/tests/test_particles.py +3 -2
- {nameparser-2.4.2 → nameparser-2.6.0}/tests/test_suffixes.py +11 -11
- {nameparser-2.4.2 → nameparser-2.6.0}/tests/test_titles.py +3 -3
- nameparser-2.6.0/tests/v2/_differential_fixtures.py +127 -0
- {nameparser-2.4.2 → nameparser-2.6.0}/tests/v2/cases.py +2171 -425
- nameparser-2.6.0/tests/v2/design_docs.py +498 -0
- {nameparser-2.4.2 → nameparser-2.6.0}/tests/v2/pipeline/test_assemble.py +5 -2
- {nameparser-2.4.2 → nameparser-2.6.0}/tests/v2/pipeline/test_assign.py +2 -2
- {nameparser-2.4.2 → nameparser-2.6.0}/tests/v2/pipeline/test_classify.py +55 -21
- {nameparser-2.4.2 → nameparser-2.6.0}/tests/v2/pipeline/test_comma.py +10 -10
- {nameparser-2.4.2 → nameparser-2.6.0}/tests/v2/pipeline/test_group.py +36 -30
- {nameparser-2.4.2 → nameparser-2.6.0}/tests/v2/pipeline/test_pieces.py +32 -25
- {nameparser-2.4.2 → nameparser-2.6.0}/tests/v2/pipeline/test_post_rules.py +65 -20
- {nameparser-2.4.2 → nameparser-2.6.0}/tests/v2/pipeline/test_script_segment.py +4 -2
- {nameparser-2.4.2 → nameparser-2.6.0}/tests/v2/pipeline/test_segment.py +2 -2
- {nameparser-2.4.2 → nameparser-2.6.0}/tests/v2/pipeline/test_tokenize.py +43 -0
- {nameparser-2.4.2 → nameparser-2.6.0}/tests/v2/pipeline/test_vocab.py +42 -8
- nameparser-2.6.0/tests/v2/rules_budget.py +154 -0
- nameparser-2.6.0/tests/v2/rules_doc.py +365 -0
- nameparser-2.6.0/tests/v2/rules_skeleton.py +482 -0
- {nameparser-2.4.2 → nameparser-2.6.0}/tests/v2/test_benchmark.py +28 -19
- {nameparser-2.4.2 → nameparser-2.6.0}/tests/v2/test_cases.py +77 -7
- nameparser-2.6.0/tests/v2/test_cldr_agreement.py +573 -0
- {nameparser-2.4.2 → nameparser-2.6.0}/tests/v2/test_config_shim.py +19 -1
- {nameparser-2.4.2 → nameparser-2.6.0}/tests/v2/test_contracts.py +15 -11
- {nameparser-2.4.2 → nameparser-2.6.0}/tests/v2/test_corpora.py +23 -13
- nameparser-2.6.0/tests/v2/test_corpus_build.py +182 -0
- nameparser-2.6.0/tests/v2/test_design_docs.py +556 -0
- {nameparser-2.4.2 → nameparser-2.6.0}/tests/v2/test_differential.py +20 -6
- nameparser-2.6.0/tests/v2/test_doc_citations.py +659 -0
- {nameparser-2.4.2 → nameparser-2.6.0}/tests/v2/test_doc_spellings.py +1 -1
- {nameparser-2.4.2 → nameparser-2.6.0}/tests/v2/test_facade.py +31 -43
- {nameparser-2.4.2 → nameparser-2.6.0}/tests/v2/test_facade_cases.py +55 -1
- nameparser-2.6.0/tests/v2/test_gnd_corpus.py +301 -0
- {nameparser-2.4.2 → nameparser-2.6.0}/tests/v2/test_lexicon.py +70 -7
- nameparser-2.6.0/tests/v2/test_local_agreement.py +300 -0
- nameparser-2.6.0/tests/v2/test_local_corpora.py +4106 -0
- nameparser-2.6.0/tests/v2/test_local_jrc.py +548 -0
- {nameparser-2.4.2 → nameparser-2.6.0}/tests/v2/test_locales.py +17 -14
- nameparser-2.6.0/tests/v2/test_name_usage_tool.py +2295 -0
- {nameparser-2.4.2 → nameparser-2.6.0}/tests/v2/test_order_correspondence.py +19 -19
- {nameparser-2.4.2 → nameparser-2.6.0}/tests/v2/test_parser.py +91 -42
- {nameparser-2.4.2 → nameparser-2.6.0}/tests/v2/test_policy.py +8 -4
- {nameparser-2.4.2 → nameparser-2.6.0}/tests/v2/test_properties.py +295 -58
- {nameparser-2.4.2 → nameparser-2.6.0}/tests/v2/test_regex_sync.py +9 -2
- {nameparser-2.4.2 → nameparser-2.6.0}/tests/v2/test_render.py +222 -49
- nameparser-2.6.0/tests/v2/test_rules_budget.py +182 -0
- {nameparser-2.4.2 → nameparser-2.6.0}/tests/v2/test_rules_doc.py +117 -8
- nameparser-2.6.0/tests/v2/test_rules_doc_grammar.py +236 -0
- nameparser-2.6.0/tests/v2/test_rules_skeleton.py +489 -0
- nameparser-2.6.0/tests/v2/test_snapshot.py +150 -0
- nameparser-2.6.0/tests/v2/test_vocab.py +885 -0
- nameparser-2.6.0/tests/v2/test_wikidata_corpus.py +429 -0
- nameparser-2.6.0/tests/v2/test_wikidata_native_corpus.py +480 -0
- nameparser-2.6.0/tests/v2/test_zh_surnames_tool.py +145 -0
- nameparser-2.4.2/nameparser/config/_invariants.py +0 -53
- nameparser-2.4.2/nameparser/config/bound_given_names.py +0 -60
- nameparser-2.4.2/nameparser/config/capitalization.py +0 -76
- nameparser-2.4.2/nameparser/config/conjunctions.py +0 -92
- nameparser-2.4.2/nameparser/config/particles.py +0 -303
- nameparser-2.4.2/nameparser/config/suffixes.py +0 -1023
- nameparser-2.4.2/nameparser/config/titles.py +0 -1026
- nameparser-2.4.2/tests/v2/_differential_fixtures.py +0 -55
- nameparser-2.4.2/tests/v2/rules_doc.py +0 -269
- nameparser-2.4.2/tests/v2/test_cldr_agreement.py +0 -277
- nameparser-2.4.2/tests/v2/test_doc_citations.py +0 -393
- nameparser-2.4.2/tests/v2/test_name_usage_tool.py +0 -303
- nameparser-2.4.2/tests/v2/test_rules_doc_grammar.py +0 -168
- nameparser-2.4.2/tests/v2/test_snapshot.py +0 -48
- {nameparser-2.4.2 → nameparser-2.6.0}/AUTHORS +0 -0
- {nameparser-2.4.2 → nameparser-2.6.0}/LICENSE +0 -0
- {nameparser-2.4.2 → nameparser-2.6.0}/MANIFEST.in +0 -0
- {nameparser-2.4.2 → nameparser-2.6.0}/nameparser/__init__.py +0 -0
- {nameparser-2.4.2 → nameparser-2.6.0}/nameparser/__main__.py +0 -0
- {nameparser-2.4.2 → nameparser-2.6.0}/nameparser/_locale.py +0 -0
- {nameparser-2.4.2 → nameparser-2.6.0}/nameparser/_pipeline/__init__.py +0 -0
- {nameparser-2.4.2 → nameparser-2.6.0}/nameparser/config/_deprecated.py +0 -0
- {nameparser-2.4.2 → nameparser-2.6.0}/nameparser/config/bound_first_names.py +0 -0
- {nameparser-2.4.2 → nameparser-2.6.0}/nameparser/config/prefixes.py +0 -0
- {nameparser-2.4.2 → nameparser-2.6.0}/nameparser/config/regexes.py +0 -0
- {nameparser-2.4.2 → nameparser-2.6.0}/nameparser/config/surnames.py +0 -0
- {nameparser-2.4.2 → nameparser-2.6.0}/nameparser/locales/__init__.py +0 -0
- {nameparser-2.4.2 → nameparser-2.6.0}/nameparser/locales/ru.py +0 -0
- {nameparser-2.4.2 → nameparser-2.6.0}/nameparser/locales/tr_az.py +0 -0
- {nameparser-2.4.2 → nameparser-2.6.0}/nameparser/parser.py +0 -0
- {nameparser-2.4.2 → nameparser-2.6.0}/nameparser/py.typed +0 -0
- {nameparser-2.4.2 → nameparser-2.6.0}/nameparser/util.py +0 -0
- {nameparser-2.4.2 → nameparser-2.6.0}/nameparser.egg-info/dependency_links.txt +0 -0
- {nameparser-2.4.2 → nameparser-2.6.0}/nameparser.egg-info/requires.txt +0 -0
- {nameparser-2.4.2 → nameparser-2.6.0}/nameparser.egg-info/top_level.txt +0 -0
- {nameparser-2.4.2 → nameparser-2.6.0}/setup.cfg +0 -0
- {nameparser-2.4.2 → nameparser-2.6.0}/tests/__init__.py +0 -0
- {nameparser-2.4.2 → nameparser-2.6.0}/tests/base.py +0 -0
- {nameparser-2.4.2 → nameparser-2.6.0}/tests/test_bound_given_names.py +0 -0
- {nameparser-2.4.2 → nameparser-2.6.0}/tests/test_brute_force.py +0 -0
- {nameparser-2.4.2 → nameparser-2.6.0}/tests/test_comma_variants.py +0 -0
- {nameparser-2.4.2 → nameparser-2.6.0}/tests/test_conjunctions.py +0 -0
- {nameparser-2.4.2 → nameparser-2.6.0}/tests/test_constants.py +0 -0
- {nameparser-2.4.2 → nameparser-2.6.0}/tests/test_east_slavic_patronymic_order.py +0 -0
- {nameparser-2.4.2 → nameparser-2.6.0}/tests/test_first_name.py +0 -0
- {nameparser-2.4.2 → nameparser-2.6.0}/tests/test_middle_name_as_last.py +0 -0
- {nameparser-2.4.2 → nameparser-2.6.0}/tests/test_nicknames.py +0 -0
- {nameparser-2.4.2 → nameparser-2.6.0}/tests/test_output_format.py +0 -0
- {nameparser-2.4.2 → nameparser-2.6.0}/tests/test_python_api.py +0 -0
- {nameparser-2.4.2 → nameparser-2.6.0}/tests/test_turkic_patronymic_order.py +0 -0
- {nameparser-2.4.2 → nameparser-2.6.0}/tests/test_variations.py +0 -0
- {nameparser-2.4.2 → nameparser-2.6.0}/tests/v2/__init__.py +0 -0
- {nameparser-2.4.2 → nameparser-2.6.0}/tests/v2/conftest.py +0 -0
- {nameparser-2.4.2 → nameparser-2.6.0}/tests/v2/pipeline/__init__.py +0 -0
- {nameparser-2.4.2 → nameparser-2.6.0}/tests/v2/pipeline/test_extract.py +0 -0
- {nameparser-2.4.2 → nameparser-2.6.0}/tests/v2/pipeline/test_state.py +0 -0
- {nameparser-2.4.2 → nameparser-2.6.0}/tests/v2/test_cldr_corpus.py +0 -0
- {nameparser-2.4.2 → nameparser-2.6.0}/tests/v2/test_cli.py +0 -0
- {nameparser-2.4.2 → nameparser-2.6.0}/tests/v2/test_config_aliases.py +0 -0
- {nameparser-2.4.2 → nameparser-2.6.0}/tests/v2/test_layering.py +0 -0
- {nameparser-2.4.2 → nameparser-2.6.0}/tests/v2/test_locale.py +0 -0
- {nameparser-2.4.2 → nameparser-2.6.0}/tests/v2/test_reprs.py +0 -0
- {nameparser-2.4.2 → nameparser-2.6.0}/tests/v2/test_types.py +0 -0
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nameparser
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.6.0
|
|
4
4
|
Summary: A simple Python module for parsing human names into their individual components.
|
|
5
5
|
Author-email: Derek Gulbranson <derek73@gmail.com>
|
|
6
|
-
License: LGPL
|
|
6
|
+
License-Expression: LGPL-2.1-or-later
|
|
7
7
|
Keywords: names,parser
|
|
8
8
|
Classifier: Intended Audience :: Developers
|
|
9
|
-
Classifier: License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)
|
|
10
9
|
Classifier: Operating System :: OS Independent
|
|
11
10
|
Classifier: Programming Language :: Python
|
|
12
11
|
Classifier: Programming Language :: Python :: 3
|
|
@@ -36,14 +35,13 @@ nameparser parses human names into seven fields — title, given, middle,
|
|
|
36
35
|
family, suffix, nickname, maiden. Results are immutable, configuration is
|
|
37
36
|
composable, and locale packs are opt-in.
|
|
38
37
|
|
|
39
|
-
📣 **nameparser 2.
|
|
40
|
-
|
|
41
|
-
``
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
details.
|
|
38
|
+
📣 **nameparser 2.6 is out.** It keeps surnames that earlier releases
|
|
39
|
+
read as credentials (``Luo Si``, ``Maria de Sa``, ``Ram Bahadur B.K.``),
|
|
40
|
+
reads ``Wm`` and ``Md`` as the given names they abbreviate, recognizes more credentials after a comma
|
|
41
|
+
(``John Smith, MBChB``), and adds an opt-in reading for names with two
|
|
42
|
+
family names (``Gabriel García Márquez``). The `release log
|
|
43
|
+
<https://nameparser.readthedocs.io/en/latest/release_log.html>`__ has
|
|
44
|
+
the details.
|
|
47
45
|
|
|
48
46
|
Chinese, Japanese and Korean names written in their own scripts are read
|
|
49
47
|
family-first, unspaced Korean names are split against the census surname
|
|
@@ -7,14 +7,13 @@ nameparser parses human names into seven fields — title, given, middle,
|
|
|
7
7
|
family, suffix, nickname, maiden. Results are immutable, configuration is
|
|
8
8
|
composable, and locale packs are opt-in.
|
|
9
9
|
|
|
10
|
-
📣 **nameparser 2.
|
|
11
|
-
|
|
12
|
-
``
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
details.
|
|
10
|
+
📣 **nameparser 2.6 is out.** It keeps surnames that earlier releases
|
|
11
|
+
read as credentials (``Luo Si``, ``Maria de Sa``, ``Ram Bahadur B.K.``),
|
|
12
|
+
reads ``Wm`` and ``Md`` as the given names they abbreviate, recognizes more credentials after a comma
|
|
13
|
+
(``John Smith, MBChB``), and adds an opt-in reading for names with two
|
|
14
|
+
family names (``Gabriel García Márquez``). The `release log
|
|
15
|
+
<https://nameparser.readthedocs.io/en/latest/release_log.html>`__ has
|
|
16
|
+
the details.
|
|
18
17
|
|
|
19
18
|
Chinese, Japanese and Korean names written in their own scripts are read
|
|
20
19
|
family-first, unspaced Korean names are split against the census surname
|
|
@@ -1048,6 +1048,7 @@ class Constants:
|
|
|
1048
1048
|
from nameparser.config.maiden_markers import MAIDEN_MARKERS
|
|
1049
1049
|
from nameparser.config.suffixes import GLUED_HONORIFICS
|
|
1050
1050
|
from nameparser.config.surnames import KOREAN_SURNAMES
|
|
1051
|
+
from nameparser.config.titles import AFTER_GIVEN_TITLES
|
|
1051
1052
|
# Every set field and every capitalization_exceptions key passes
|
|
1052
1053
|
# _v1_matchable BEFORE any set algebra below: an entry v1 could
|
|
1053
1054
|
# never match, or one Lexicon folds to empty (#582), is dropped
|
|
@@ -1109,7 +1110,7 @@ class Constants:
|
|
|
1109
1110
|
# tests/v2/test_config_shim.py::test_snapshot_field_translation)
|
|
1110
1111
|
# A capitalization_exceptions value that does not spell its key
|
|
1111
1112
|
# is the one DECIDED exception to this method's never-raise
|
|
1112
|
-
# rule (#459, decisions
|
|
1113
|
+
# rule (#459, decisions/R4 and #3-0-reevaluations).
|
|
1113
1114
|
# _normpairs raises _MaskValueError for exactly that shape, and
|
|
1114
1115
|
# the except clause re-spells its offered fix for a v1 caller.
|
|
1115
1116
|
# Caught by TYPE, so every other Lexicon error passes through
|
|
@@ -1160,8 +1161,8 @@ class Constants:
|
|
|
1160
1161
|
particles=particles,
|
|
1161
1162
|
# complement translation: v1 marks the never-given subset;
|
|
1162
1163
|
# v2 marks the may-be-given subset. The trailing union keeps
|
|
1163
|
-
# a config v1 accepted:
|
|
1164
|
-
#
|
|
1164
|
+
# a config v1 accepted: the vocabulary build refuses a
|
|
1165
|
+
# shipped word in both NON_GIVEN_NAME_PARTICLES and
|
|
1165
1166
|
# BOUND_GIVEN_NAMES, but nothing stops a caller adding one at
|
|
1166
1167
|
# runtime, and v1 then lets the bound rule win (leading "dos
|
|
1167
1168
|
# Santos Silva" parses first="dos Santos"). Treating such a
|
|
@@ -1207,6 +1208,14 @@ class Constants:
|
|
|
1207
1208
|
# stops mattering, the same rule ambiguous_acronyms gets
|
|
1208
1209
|
# against suffix_acronyms above.
|
|
1209
1210
|
honorific_tails=GLUED_HONORIFICS & suffix_words,
|
|
1211
|
+
# likewise no v1 manager: the salutations ending a given
|
|
1212
|
+
# part are 2.6 behavior (#835), so they ride in the
|
|
1213
|
+
# snapshot only, and the one v1 knob that reaches them is
|
|
1214
|
+
# deleting the title. Lexicon does not check this pair
|
|
1215
|
+
# (classify reads a member only where it is a title too),
|
|
1216
|
+
# so the intersection is kept for `_snapshot() ==
|
|
1217
|
+
# Lexicon.default()` legibility, as conjunctions_ambiguous'.
|
|
1218
|
+
after_given_titles=AFTER_GIVEN_TITLES & titles,
|
|
1210
1219
|
# TupleManager is dict[str, object] (v1 parity: values were
|
|
1211
1220
|
# never statically str-typed); every real entry is a str,
|
|
1212
1221
|
# same assumption _DelimiterManager's sentinel lookup makes.
|
|
@@ -271,7 +271,7 @@ class HumanName:
|
|
|
271
271
|
"""Re-capitalize the current parse against the bound lexicon.
|
|
272
272
|
force=None reads the bound Constants' render default
|
|
273
273
|
(force_mixed_case_capitalization); the core's capitalized()
|
|
274
|
-
implements the one-case gate (rules
|
|
274
|
+
implements the one-case gate (rules/R5, suffixes left out
|
|
275
275
|
since #492) -- not
|
|
276
276
|
re-implemented here."""
|
|
277
277
|
self._resolve()
|
|
@@ -511,7 +511,7 @@ class HumanName:
|
|
|
511
511
|
# answers -- the same fallback _render._cap_word takes for the
|
|
512
512
|
# same tokens and through the same helper, which is why the
|
|
513
513
|
# helper is imported rather than the predicate rewritten
|
|
514
|
-
# (decisions
|
|
514
|
+
# (decisions/R4 for why one question and not two: whether a
|
|
515
515
|
# word is a connective is a fact the word can answer alone,
|
|
516
516
|
# whether a particle is acting as one is a fact about the part).
|
|
517
517
|
#
|
|
@@ -533,7 +533,7 @@ class HumanName:
|
|
|
533
533
|
and UNJOINED_CONJUNCTION_TAG not in tok.tags)
|
|
534
534
|
|
|
535
535
|
def _split_last(self) -> tuple[list[str], list[str]]:
|
|
536
|
-
# rules
|
|
536
|
+
# rules/R2: "a name part whose every word is particle
|
|
537
537
|
# vocabulary is a part where none of them is doing a
|
|
538
538
|
# particle's work" -- the all-particle guard
|
|
539
539
|
# below is this rule, and predates its statement: v1 assumed a
|
|
@@ -617,7 +617,7 @@ class HumanName:
|
|
|
617
617
|
#
|
|
618
618
|
# Particles are NOT decided per token: _is_particle stays a
|
|
619
619
|
# live vocabulary lookup, as _render._cap_word keeps it --
|
|
620
|
-
# rules
|
|
620
|
+
# rules/R4 draws this boundary per question, not per field.
|
|
621
621
|
self._resolve()
|
|
622
622
|
if tokens is None:
|
|
623
623
|
# STRING PATH. split() rather than split(" ") because
|
|
@@ -700,10 +700,11 @@ class HumanName:
|
|
|
700
700
|
if got or not words or not all(self._is_particle(w)
|
|
701
701
|
for w in words):
|
|
702
702
|
return got
|
|
703
|
-
# rules
|
|
704
|
-
#
|
|
705
|
-
# -- nothing survived
|
|
706
|
-
# the filter, so the whole group is particles
|
|
703
|
+
# rules/R3: "A part whose every word is a particle initials
|
|
704
|
+
# those particles: they are not acting as particles there"
|
|
705
|
+
# -- the Except bullet this quote opens; nothing survived
|
|
706
|
+
# the filter, so the whole group is particles
|
|
707
|
+
# (decisions/R3). The
|
|
707
708
|
# facade's twin of the core's
|
|
708
709
|
# UNJOINED_TAG. NOT pinned against it by the case runners,
|
|
709
710
|
# which compare the seven role fields only (Case carries
|
|
@@ -25,7 +25,7 @@ _VOCAB_FIELDS = (
|
|
|
25
25
|
"titles", "given_name_titles", "suffix_acronyms", "suffix_words",
|
|
26
26
|
"suffix_acronyms_ambiguous", "particles", "particles_ambiguous",
|
|
27
27
|
"conjunctions", "conjunctions_ambiguous", "bound_given_names",
|
|
28
|
-
"maiden_markers", "surnames", "honorific_tails",
|
|
28
|
+
"maiden_markers", "surnames", "honorific_tails", "after_given_titles",
|
|
29
29
|
)
|
|
30
30
|
|
|
31
31
|
#: (marker, base, why) triples. Each marker QUALIFIES how entries of
|
|
@@ -55,21 +55,19 @@ _VOCAB_FIELDS = (
|
|
|
55
55
|
#: as a tail would peel a period-gated word off a real name), and
|
|
56
56
|
#: nothing needs the acronym half: the shipped tails are CJK
|
|
57
57
|
#: honorifics, which are words.
|
|
58
|
-
#: The
|
|
59
|
-
#:
|
|
60
|
-
#:
|
|
61
|
-
#: is
|
|
62
|
-
#: raises unconditionally -- so under -O this is what still holds the
|
|
63
|
-
#: SHIPPED vocabulary to the invariant, as it is the only thing that
|
|
64
|
-
#: ever held a caller's own.
|
|
58
|
+
#: The shipped vocabulary holds the relation by construction: its
|
|
59
|
+
#: build puts a word in GLUED_HONORIFICS only through an `attached`
|
|
60
|
+
#: property on a word reading (tools/vocab/build.py). The check here
|
|
61
|
+
#: is the only thing that holds a caller's own.
|
|
65
62
|
#:
|
|
66
|
-
#:
|
|
63
|
+
#: Three fields are deliberately NOT here and have no check of their own.
|
|
67
64
|
#: given_name_titles -- see the note in __post_init__ for why every
|
|
68
65
|
#: attempt at one rejected working configurations. conjunctions_ambiguous
|
|
69
66
|
#: -- an orphan is never consulted because both the classify fork and
|
|
70
67
|
#: its ambiguity emitter require the base entry as well, so an orphan
|
|
71
68
|
#: decides nothing, and AGENTS.md's invariants rule guards harm, not
|
|
72
|
-
#: no-ops.
|
|
69
|
+
#: no-ops. after_given_titles -- the same: classify tags a word from it
|
|
70
|
+
#: only where the word is in titles too, so an orphan is never read.
|
|
73
71
|
_SUBSET_FIELDS = (
|
|
74
72
|
("particles_ambiguous", "particles",
|
|
75
73
|
"an orphan emits a spurious particle-or-given ambiguity"),
|
|
@@ -129,7 +127,7 @@ _PHRASE_FIELDS = ("given_name_titles", "maiden_markers")
|
|
|
129
127
|
#: it is roman five -- suffix -- where 'V.' stays a middle
|
|
130
128
|
#: initial through that same carve-out; 'Jr。' simply reaches its
|
|
131
129
|
#: entry, and 'Dr。' its title.
|
|
132
|
-
#: decisions
|
|
130
|
+
#: decisions/cjk-full-stops carries the measurements.
|
|
133
131
|
FULL_STOPS = "..。。"
|
|
134
132
|
|
|
135
133
|
|
|
@@ -152,7 +150,7 @@ def _normalize(word: str) -> str:
|
|
|
152
150
|
|
|
153
151
|
NFC, since #322, and only for a non-ASCII word (ASCII is already
|
|
154
152
|
NFC, and this runs per token on the parse path --
|
|
155
|
-
decisions
|
|
153
|
+
decisions/parse-cost). NFD hangul decomposes to jamo and missed
|
|
156
154
|
every vocabulary entry; composing at lookup AND at storage keeps
|
|
157
155
|
the two symmetric, and no shipped entry changes under it (measured
|
|
158
156
|
2026-09-09: fold every string member of every Lexicon.default()
|
|
@@ -175,7 +173,7 @@ def _normalize(word: str) -> str:
|
|
|
175
173
|
word = stripped
|
|
176
174
|
|
|
177
175
|
|
|
178
|
-
# rules
|
|
176
|
+
# rules/P7: "A one-letter word written with its period is an
|
|
179
177
|
# initial and not a particle"
|
|
180
178
|
def _spells_an_initial(n: str, text: str) -> bool:
|
|
181
179
|
"""Whether `text` is ONE letter written with its period ('Ó.',
|
|
@@ -195,7 +193,7 @@ def _spells_an_initial(n: str, text: str) -> bool:
|
|
|
195
193
|
-- its script table is _policy's, which this module may not import
|
|
196
194
|
-- so a caller's one-character particle in such a script, written
|
|
197
195
|
with a period, is vetoed where is_initial would call it no initial
|
|
198
|
-
(decisions
|
|
196
|
+
(decisions/P7). No shipped particle is one character of those
|
|
199
197
|
scripts."""
|
|
200
198
|
return len(n) == 1 and text[-1] == "."
|
|
201
199
|
|
|
@@ -224,7 +222,7 @@ def _fold_words(words: Iterable[str]) -> list[str]:
|
|
|
224
222
|
|
|
225
223
|
map/filter rather than a comprehension: both are C calls where a
|
|
226
224
|
comprehension is a Python frame on 3.11, and this runs on the parse
|
|
227
|
-
path for every name carrying a title run (decisions
|
|
225
|
+
path for every name carrying a title run (decisions/parse-cost).
|
|
228
226
|
"""
|
|
229
227
|
return list(filter(None, map(_normalize, words)))
|
|
230
228
|
|
|
@@ -247,7 +245,7 @@ def _run_addresses_by_given(words: Iterable[str],
|
|
|
247
245
|
|
|
248
246
|
Two sites ask it and they must agree -- post_rules for H1, group
|
|
249
247
|
for the P5 licence -- because a run read two ways is a rule
|
|
250
|
-
contradicting itself (decisions
|
|
248
|
+
contradicting itself (decisions/P5, the 2026-08-22 #369 entry).
|
|
251
249
|
One predicate, so they cannot drift.
|
|
252
250
|
|
|
253
251
|
The WHOLE run's key, or that key's LAST word (#489): a run is
|
|
@@ -542,7 +540,7 @@ def _normpairs(
|
|
|
542
540
|
# punctuation marks which letters are joined into one run
|
|
543
541
|
# (_render._apply_mask reads that) and is never written into
|
|
544
542
|
# the word. A raise, not a warning: a mismatched value used to
|
|
545
|
-
# be SUBSTITUTED for the word, and rules
|
|
543
|
+
# be SUBSTITUTED for the word, and rules/R4: "Repair
|
|
546
544
|
# changes case and nothing else", so there is no reading of
|
|
547
545
|
# one that repair can honor.
|
|
548
546
|
if _alnum(_normalize(v)) != _alnum(normalized_key):
|
|
@@ -582,8 +580,8 @@ class Lexicon:
|
|
|
582
580
|
#: Pre-nominal titles ("dr", "sir", "capt", ...). Full default
|
|
583
581
|
#: list: :data:`~nameparser.config.titles.TITLES`.
|
|
584
582
|
titles: frozenset[str] = frozenset()
|
|
585
|
-
#: Titles whose single
|
|
586
|
-
#: ("sheikh", "sister", ...) rather than a family name. Full
|
|
583
|
+
#: Titles whose single name word, before or after them, reads as a
|
|
584
|
+
#: GIVEN name ("sheikh", "sister", ...) rather than a family name. Full
|
|
587
585
|
#: default list: :data:`~nameparser.config.titles.GIVEN_NAME_TITLES`.
|
|
588
586
|
given_name_titles: frozenset[str] = frozenset()
|
|
589
587
|
#: Post-nominal acronym suffixes, matched with or without periods
|
|
@@ -599,7 +597,7 @@ class Lexicon:
|
|
|
599
597
|
#: outright; written bare, capitals decide in a mixed-case name
|
|
600
598
|
#: ("Jack MA" is the credential, "Jack Ma" keeps his family name)
|
|
601
599
|
#: and otherwise the word counts as a suffix only where a name
|
|
602
|
-
#: word is left beside it ("John Smith ma"), rules.
|
|
600
|
+
#: word is left beside it ("John Smith ma"), rules/S6 and rules/S4. Full
|
|
603
601
|
#: default list:
|
|
604
602
|
#: :data:`~nameparser.config.suffixes.SUFFIX_ACRONYMS_AMBIGUOUS`.
|
|
605
603
|
suffix_acronyms_ambiguous: frozenset[str] = frozenset()
|
|
@@ -644,8 +642,10 @@ class Lexicon:
|
|
|
644
642
|
#: decided by the writing instead and never consults this set, and
|
|
645
643
|
#: neither does a caseless letter (Arabic و), which has no case to
|
|
646
644
|
#: read. A member additionally reports
|
|
647
|
-
#: :attr:`~nameparser.AmbiguityKind.CONJUNCTION_OR_INITIAL
|
|
648
|
-
#:
|
|
645
|
+
#: :attr:`~nameparser.AmbiguityKind.CONJUNCTION_OR_INITIAL` where a
|
|
646
|
+
#: name word of its part follows it (with none, it connects
|
|
647
|
+
#: nothing); a non-member reports nothing, its reading not being in
|
|
648
|
+
#: doubt.
|
|
649
649
|
#: Full default list:
|
|
650
650
|
#: :data:`~nameparser.config.conjunctions.CONJUNCTIONS_AMBIGUOUS`.
|
|
651
651
|
#: Entries need not also be in ``conjunctions``; one that is not is
|
|
@@ -673,8 +673,8 @@ class Lexicon:
|
|
|
673
673
|
#: (:data:`~nameparser.config.surnames.KOREAN_SURNAMES`); Chinese
|
|
674
674
|
#: surnames ship in locales.ZH because Han segmentation is opt-in.
|
|
675
675
|
surnames: frozenset[str] = frozenset()
|
|
676
|
-
#: Honorifics
|
|
677
|
-
#: (#308), matched longest-first: 田中さん splits into 田中 and さん
|
|
676
|
+
#: Honorifics, and credential, office and generational words, that
|
|
677
|
+
#: may be peeled off the END of a name token (#308, #707), matched longest-first: 田中さん splits into 田中 and さん
|
|
678
678
|
#: before the tokens are classified. Every entry must also be a
|
|
679
679
|
#: :attr:`suffix_words` entry -- the peeled tail is claimed by
|
|
680
680
|
#: suffix classification like any other post-nominal. Deliberately
|
|
@@ -690,6 +690,15 @@ class Lexicon:
|
|
|
690
690
|
#: vocabulary is. Full default list:
|
|
691
691
|
#: :data:`~nameparser.config.suffixes.GLUED_HONORIFICS`.
|
|
692
692
|
honorific_tails: frozenset[str] = frozenset()
|
|
693
|
+
#: Salutations that read as a title where one ENDS the given part
|
|
694
|
+
#: after a family comma, written bare: "Doe, Jane Mrs" reads title
|
|
695
|
+
#: "Mrs" where an office or rank word there stays a middle name
|
|
696
|
+
#: ("Doe, John King"), rules/H8. A period-marked title word reads as
|
|
697
|
+
#: a title there whether listed here or not (rules/H5). Entries need
|
|
698
|
+
#: not also be in ``titles``; one that is not is never consulted.
|
|
699
|
+
#: Full default list:
|
|
700
|
+
#: :data:`~nameparser.config.titles.AFTER_GIVEN_TITLES`.
|
|
701
|
+
after_given_titles: frozenset[str] = frozenset()
|
|
693
702
|
#: Lowercase word -> case mask used by capitalized(): the word's
|
|
694
703
|
#: own letters and digits, each in the case it takes ("phd" ->
|
|
695
704
|
#: "PhD"), laid over the word as written, so the one entry
|
|
@@ -737,9 +746,10 @@ class Lexicon:
|
|
|
737
746
|
# the expensive one -- three working configurations broken
|
|
738
747
|
# across two attempts. Do not add a third.
|
|
739
748
|
#
|
|
740
|
-
# The v2 form of
|
|
741
|
-
#
|
|
742
|
-
#
|
|
749
|
+
# The v2 form of the vocabulary build's check that a bound given
|
|
750
|
+
# name which is also a particle may be given (tools/vocab/build.py,
|
|
751
|
+
# check 9). The build guards the shipped data; this guards
|
|
752
|
+
# vocabulary a caller supplies.
|
|
743
753
|
contradictory = (
|
|
744
754
|
self.bound_given_names & self.particles) - self.particles_ambiguous
|
|
745
755
|
if contradictory:
|
|
@@ -945,7 +955,8 @@ def _default_lexicon() -> Lexicon:
|
|
|
945
955
|
SUFFIX_WORDS,
|
|
946
956
|
)
|
|
947
957
|
from nameparser.config.surnames import KOREAN_SURNAMES
|
|
948
|
-
from nameparser.config.titles import
|
|
958
|
+
from nameparser.config.titles import (
|
|
959
|
+
AFTER_GIVEN_TITLES, GIVEN_NAME_TITLES, TITLES)
|
|
949
960
|
|
|
950
961
|
# every vocabulary constant is a frozenset since #293, so each one
|
|
951
962
|
# feeds its strictly-typed frozenset[str] field as it stands -- and
|
|
@@ -973,6 +984,7 @@ def _default_lexicon() -> Lexicon:
|
|
|
973
984
|
maiden_markers=MAIDEN_MARKERS,
|
|
974
985
|
surnames=KOREAN_SURNAMES,
|
|
975
986
|
honorific_tails=GLUED_HONORIFICS,
|
|
987
|
+
after_given_titles=AFTER_GIVEN_TITLES,
|
|
976
988
|
# pass canonical pair-tuples so this strictly-typed call site never
|
|
977
989
|
# feeds a Mapping to the tuple-annotated field; __post_init__
|
|
978
990
|
# still tolerates a Mapping at runtime for interactive use
|
|
@@ -41,7 +41,7 @@ class Parser:
|
|
|
41
41
|
vocabulary DECLINES, so a locale pack's surnames always win where
|
|
42
42
|
they match; returning None declines in turn and the token stays
|
|
43
43
|
whole. Two promises narrow when one is supplied (the first is
|
|
44
|
-
rules
|
|
44
|
+
rules/A1's Accepted bullet "The one exception to totality"):
|
|
45
45
|
parse-totality gains its one exception -- an exception raised by
|
|
46
46
|
the segmenter propagates, because a user-supplied callable's own
|
|
47
47
|
error is a user-code error, not a content error -- and this Parser
|
|
@@ -215,12 +215,12 @@ class Parser:
|
|
|
215
215
|
dropped = set(state.dropped)
|
|
216
216
|
# Force the role BEFORE the entry pass: it keys on
|
|
217
217
|
# Role.SUFFIX, and a bare value's sub-parse reads its words
|
|
218
|
-
# as a name ('
|
|
218
|
+
# as a name ('RA PhD' is a title and a family there), so
|
|
219
219
|
# inside the sub-parse it joined nothing. The sub-parse's
|
|
220
220
|
# tags are KEPT, "joined" included: a within-piece mark
|
|
221
221
|
# (the Ph. D. merge) is role-blind and right for every
|
|
222
222
|
# role, and a clear was measured and backed out --
|
|
223
|
-
# decisions
|
|
223
|
+
# decisions/R1 (2026-09-06 #511) carries the rest: the
|
|
224
224
|
# between-piece mark that rides onto a non-suffix role, and
|
|
225
225
|
# which views read it. Dropped tokens keep their role: the
|
|
226
226
|
# pass filters them by index and assemble omits them.
|
|
@@ -228,7 +228,7 @@ class Parser:
|
|
|
228
228
|
tok if i in dropped
|
|
229
229
|
else dataclasses.replace(tok, role=role)
|
|
230
230
|
for i, tok in enumerate(state.tokens))
|
|
231
|
-
# rules
|
|
231
|
+
# rules/R1: "a run of post-nominals written with spaces
|
|
232
232
|
# renders with spaces, and one written with commas keeps
|
|
233
233
|
# them" -- the same pass post_rules runs last, over the
|
|
234
234
|
# value's own comma offsets, so the harvest carries exactly
|
|
@@ -346,13 +346,13 @@ def parser_for(*locales: Locale, base: Parser | None = None,
|
|
|
346
346
|
# a subclass with extra mandatory args would break this rewrap
|
|
347
347
|
raise type(exc)(
|
|
348
348
|
f"while applying locale {loc.code!r}: {exc}") from exc
|
|
349
|
-
# rules
|
|
349
|
+
# rules/D1: "constructing a parser that activates division for
|
|
350
350
|
# scripts with no covering surnames and no segmenter warns at
|
|
351
351
|
# construction, naming the dead scripts and each way out"
|
|
352
|
-
# (history: decisions
|
|
353
|
-
# rules
|
|
352
|
+
# (history: decisions/D1)
|
|
353
|
+
# rules/D2: "applying a locale pack wraps any such error with
|
|
354
354
|
# the locale's code, so a stacked configuration names which layer
|
|
355
|
-
# broke" (history: decisions
|
|
355
|
+
# broke" (history: decisions/D2)
|
|
356
356
|
# Construction warnings (the segmenterless-activation check in
|
|
357
357
|
# Parser.__post_init__) re-emit from THIS frame: its stacklevel is
|
|
358
358
|
# sized for direct Parser(...) construction, and through this
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
Consumes: tokens (all roles set), dropped, ambiguities (by index;
|
|
4
4
|
one carrying ``field_tail`` is worded from its word's final role,
|
|
5
|
-
#626)
|
|
5
|
+
#626), and segments, for the part a withdrawn connective report's
|
|
6
|
+
letter ends (rules/P8, #721).
|
|
6
7
|
Produces: a validated ParsedName -- the constructor re-checks every
|
|
7
8
|
invariant (span order/bounds, ambiguity subset), so a pipeline bug
|
|
8
9
|
that would produce an invalid result dies HERE, not in a renderer
|
|
@@ -20,16 +21,16 @@ tests/v2/pipeline/test_assemble.py.
|
|
|
20
21
|
"""
|
|
21
22
|
from __future__ import annotations
|
|
22
23
|
|
|
23
|
-
from nameparser._pipeline._state import FIELD_READING, ParseState
|
|
24
|
+
from nameparser._pipeline._state import FIELD_READING, NAME_ROLES, ParseState
|
|
24
25
|
from nameparser._types import (
|
|
25
26
|
Ambiguity, AmbiguityKind, ParsedName, Role, Token,
|
|
26
27
|
)
|
|
27
28
|
|
|
28
29
|
|
|
29
|
-
# rules
|
|
30
|
+
# rules/A2: "a name with no name content parses to the empty
|
|
30
31
|
# name — every field empty, false as a boolean — while ambiguities
|
|
31
32
|
# born from its punctuation survive on the empty result"
|
|
32
|
-
# (history: decisions
|
|
33
|
+
# (history: decisions/A2)
|
|
33
34
|
def assemble(state: ParseState) -> ParsedName:
|
|
34
35
|
dropped = set(state.dropped)
|
|
35
36
|
final: dict[int, Token] = {}
|
|
@@ -74,6 +75,7 @@ def assemble(state: ParseState) -> ParsedName:
|
|
|
74
75
|
if contentless:
|
|
75
76
|
final = {}
|
|
76
77
|
ambiguities = []
|
|
78
|
+
segment_of: dict[int, tuple[int, ...]] | None = None
|
|
77
79
|
for pending in state.ambiguities:
|
|
78
80
|
materialized = tuple(final[i] for i in pending.indices
|
|
79
81
|
if i in final)
|
|
@@ -85,7 +87,7 @@ def assemble(state: ParseState) -> ParsedName:
|
|
|
85
87
|
# referents did not lose a contest, they were discarded
|
|
86
88
|
# wholesale, and the report still describes the input.
|
|
87
89
|
continue
|
|
88
|
-
# rules
|
|
90
|
+
# rules/A1: "a report names the reading the parse took, so
|
|
89
91
|
# a report whose fork the rest of the parse then resolved to
|
|
90
92
|
# NEITHER branch is withdrawn rather than carried beside a
|
|
91
93
|
# reading it contradicts". classify's connective-or-initial
|
|
@@ -114,6 +116,23 @@ def assemble(state: ParseState) -> ParsedName:
|
|
|
114
116
|
and any(t.role in (Role.SUFFIX, Role.TITLE)
|
|
115
117
|
for t in materialized)):
|
|
116
118
|
continue
|
|
119
|
+
# rules/P8: "unless no name word of its part stands to its
|
|
120
|
+
# right" -- nothing after the letter in its segment, or only
|
|
121
|
+
# suffixes, titles, a maiden clause or a nickname: it connects
|
|
122
|
+
# nothing, so the report goes (#721). Asked here for the reason
|
|
123
|
+
# above: the roles are final. Where the letter is also a
|
|
124
|
+
# numeral the fork read, its own report (rules/S10) is the one
|
|
125
|
+
# left, so one token never carries both. Built only for a
|
|
126
|
+
# connective report, so other names pay nothing.
|
|
127
|
+
if pending.kind is AmbiguityKind.CONJUNCTION_OR_INITIAL:
|
|
128
|
+
if segment_of is None:
|
|
129
|
+
segment_of = {j: seg for seg in state.segments
|
|
130
|
+
for j in seg}
|
|
131
|
+
i = pending.indices[0]
|
|
132
|
+
if not any(j > i and j in final
|
|
133
|
+
and final[j].role in NAME_ROLES
|
|
134
|
+
for j in segment_of.get(i, ())):
|
|
135
|
+
continue
|
|
117
136
|
detail = pending.detail
|
|
118
137
|
if pending.field_tail is not None:
|
|
119
138
|
# A report naming a field, in a name emptied above (no
|