nameparser 2.5.0__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.5.0/nameparser.egg-info → nameparser-2.6.0}/PKG-INFO +8 -9
- {nameparser-2.5.0 → nameparser-2.6.0}/README.rst +6 -6
- {nameparser-2.5.0 → nameparser-2.6.0}/nameparser/_config_shim.py +11 -2
- {nameparser-2.5.0 → nameparser-2.6.0}/nameparser/_facade.py +5 -4
- {nameparser-2.5.0 → nameparser-2.6.0}/nameparser/_lexicon.py +24 -14
- {nameparser-2.5.0 → nameparser-2.6.0}/nameparser/_parser.py +2 -2
- {nameparser-2.5.0 → nameparser-2.6.0}/nameparser/_pipeline/_assemble.py +2 -2
- {nameparser-2.5.0 → nameparser-2.6.0}/nameparser/_pipeline/_assign.py +27 -21
- {nameparser-2.5.0 → nameparser-2.6.0}/nameparser/_pipeline/_classify.py +75 -14
- {nameparser-2.5.0 → nameparser-2.6.0}/nameparser/_pipeline/_comma.py +99 -34
- {nameparser-2.5.0 → nameparser-2.6.0}/nameparser/_pipeline/_extract.py +13 -10
- {nameparser-2.5.0 → nameparser-2.6.0}/nameparser/_pipeline/_group.py +56 -204
- {nameparser-2.5.0 → nameparser-2.6.0}/nameparser/_pipeline/_pieces.py +59 -37
- {nameparser-2.5.0 → nameparser-2.6.0}/nameparser/_pipeline/_post_rules.py +116 -99
- {nameparser-2.5.0 → nameparser-2.6.0}/nameparser/_pipeline/_script_segment.py +22 -87
- {nameparser-2.5.0 → nameparser-2.6.0}/nameparser/_pipeline/_segment.py +16 -5
- {nameparser-2.5.0 → nameparser-2.6.0}/nameparser/_pipeline/_state.py +6 -5
- {nameparser-2.5.0 → nameparser-2.6.0}/nameparser/_pipeline/_tokenize.py +25 -13
- {nameparser-2.5.0 → nameparser-2.6.0}/nameparser/_pipeline/_vocab.py +97 -27
- {nameparser-2.5.0 → nameparser-2.6.0}/nameparser/_policy.py +49 -14
- {nameparser-2.5.0 → nameparser-2.6.0}/nameparser/_render.py +94 -35
- {nameparser-2.5.0 → nameparser-2.6.0}/nameparser/_types.py +3 -3
- {nameparser-2.5.0 → nameparser-2.6.0}/nameparser/_version.py +4 -4
- {nameparser-2.5.0 → nameparser-2.6.0}/nameparser/config/__init__.py +2 -0
- 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.5.0 → nameparser-2.6.0}/nameparser/config/maiden_markers.py +6 -23
- 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.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.5.0 → nameparser-2.6.0/nameparser.egg-info}/PKG-INFO +8 -9
- {nameparser-2.5.0 → nameparser-2.6.0}/nameparser.egg-info/SOURCES.txt +93 -0
- {nameparser-2.5.0 → nameparser-2.6.0}/pyproject.toml +16 -4
- {nameparser-2.5.0 → nameparser-2.6.0}/tests/conftest.py +28 -0
- {nameparser-2.5.0 → nameparser-2.6.0}/tests/test_capitalization.py +3 -3
- {nameparser-2.5.0 → nameparser-2.6.0}/tests/test_initials.py +1 -1
- {nameparser-2.5.0 → nameparser-2.6.0}/tests/test_particles.py +3 -2
- {nameparser-2.5.0 → nameparser-2.6.0}/tests/test_suffixes.py +1 -1
- {nameparser-2.5.0 → nameparser-2.6.0}/tests/v2/_differential_fixtures.py +32 -1
- {nameparser-2.5.0 → nameparser-2.6.0}/tests/v2/cases.py +1101 -107
- nameparser-2.6.0/tests/v2/design_docs.py +498 -0
- {nameparser-2.5.0 → nameparser-2.6.0}/tests/v2/pipeline/test_assemble.py +1 -1
- {nameparser-2.5.0 → nameparser-2.6.0}/tests/v2/pipeline/test_classify.py +18 -7
- {nameparser-2.5.0 → nameparser-2.6.0}/tests/v2/pipeline/test_group.py +2 -2
- {nameparser-2.5.0 → nameparser-2.6.0}/tests/v2/pipeline/test_pieces.py +23 -16
- {nameparser-2.5.0 → nameparser-2.6.0}/tests/v2/pipeline/test_post_rules.py +44 -1
- {nameparser-2.5.0 → nameparser-2.6.0}/tests/v2/pipeline/test_script_segment.py +3 -1
- {nameparser-2.5.0 → nameparser-2.6.0}/tests/v2/pipeline/test_tokenize.py +43 -0
- {nameparser-2.5.0 → nameparser-2.6.0}/tests/v2/pipeline/test_vocab.py +38 -4
- 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.5.0 → nameparser-2.6.0}/tests/v2/test_benchmark.py +15 -6
- {nameparser-2.5.0 → nameparser-2.6.0}/tests/v2/test_cases.py +58 -5
- {nameparser-2.5.0 → nameparser-2.6.0}/tests/v2/test_config_shim.py +19 -1
- {nameparser-2.5.0 → nameparser-2.6.0}/tests/v2/test_contracts.py +15 -11
- {nameparser-2.5.0 → nameparser-2.6.0}/tests/v2/test_corpora.py +8 -8
- {nameparser-2.5.0 → nameparser-2.6.0}/tests/v2/test_corpus_build.py +12 -0
- nameparser-2.6.0/tests/v2/test_design_docs.py +556 -0
- {nameparser-2.5.0 → nameparser-2.6.0}/tests/v2/test_differential.py +20 -6
- {nameparser-2.5.0 → nameparser-2.6.0}/tests/v2/test_doc_citations.py +272 -52
- {nameparser-2.5.0 → nameparser-2.6.0}/tests/v2/test_facade.py +5 -5
- {nameparser-2.5.0 → nameparser-2.6.0}/tests/v2/test_facade_cases.py +41 -1
- {nameparser-2.5.0 → nameparser-2.6.0}/tests/v2/test_lexicon.py +69 -6
- 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.5.0 → nameparser-2.6.0}/tests/v2/test_locales.py +8 -6
- nameparser-2.6.0/tests/v2/test_name_usage_tool.py +2295 -0
- {nameparser-2.5.0 → nameparser-2.6.0}/tests/v2/test_parser.py +6 -4
- {nameparser-2.5.0 → nameparser-2.6.0}/tests/v2/test_policy.py +4 -1
- {nameparser-2.5.0 → nameparser-2.6.0}/tests/v2/test_properties.py +144 -15
- {nameparser-2.5.0 → nameparser-2.6.0}/tests/v2/test_regex_sync.py +3 -1
- {nameparser-2.5.0 → nameparser-2.6.0}/tests/v2/test_render.py +144 -11
- nameparser-2.6.0/tests/v2/test_rules_budget.py +182 -0
- {nameparser-2.5.0 → nameparser-2.6.0}/tests/v2/test_rules_doc.py +111 -1
- 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.5.0/nameparser/config/_invariants.py +0 -53
- nameparser-2.5.0/nameparser/config/bound_given_names.py +0 -60
- nameparser-2.5.0/nameparser/config/capitalization.py +0 -76
- nameparser-2.5.0/nameparser/config/conjunctions.py +0 -94
- nameparser-2.5.0/nameparser/config/particles.py +0 -333
- nameparser-2.5.0/nameparser/config/suffixes.py +0 -1134
- nameparser-2.5.0/nameparser/config/titles.py +0 -1033
- nameparser-2.5.0/tests/v2/design_docs.py +0 -107
- nameparser-2.5.0/tests/v2/rules_doc.py +0 -267
- nameparser-2.5.0/tests/v2/test_design_docs.py +0 -150
- nameparser-2.5.0/tests/v2/test_name_usage_tool.py +0 -1156
- nameparser-2.5.0/tests/v2/test_rules_doc_grammar.py +0 -168
- nameparser-2.5.0/tests/v2/test_snapshot.py +0 -48
- {nameparser-2.5.0 → nameparser-2.6.0}/AUTHORS +0 -0
- {nameparser-2.5.0 → nameparser-2.6.0}/LICENSE +0 -0
- {nameparser-2.5.0 → nameparser-2.6.0}/MANIFEST.in +0 -0
- {nameparser-2.5.0 → nameparser-2.6.0}/nameparser/__init__.py +0 -0
- {nameparser-2.5.0 → nameparser-2.6.0}/nameparser/__main__.py +0 -0
- {nameparser-2.5.0 → nameparser-2.6.0}/nameparser/_locale.py +0 -0
- {nameparser-2.5.0 → nameparser-2.6.0}/nameparser/_pipeline/__init__.py +0 -0
- {nameparser-2.5.0 → nameparser-2.6.0}/nameparser/config/_deprecated.py +0 -0
- {nameparser-2.5.0 → nameparser-2.6.0}/nameparser/config/bound_first_names.py +0 -0
- {nameparser-2.5.0 → nameparser-2.6.0}/nameparser/config/prefixes.py +0 -0
- {nameparser-2.5.0 → nameparser-2.6.0}/nameparser/config/regexes.py +0 -0
- {nameparser-2.5.0 → nameparser-2.6.0}/nameparser/config/surnames.py +0 -0
- {nameparser-2.5.0 → nameparser-2.6.0}/nameparser/locales/__init__.py +0 -0
- {nameparser-2.5.0 → nameparser-2.6.0}/nameparser/locales/ja.py +0 -0
- {nameparser-2.5.0 → nameparser-2.6.0}/nameparser/locales/ru.py +0 -0
- {nameparser-2.5.0 → nameparser-2.6.0}/nameparser/locales/tr_az.py +0 -0
- {nameparser-2.5.0 → nameparser-2.6.0}/nameparser/locales/zh.py +0 -0
- {nameparser-2.5.0 → nameparser-2.6.0}/nameparser/parser.py +0 -0
- {nameparser-2.5.0 → nameparser-2.6.0}/nameparser/py.typed +0 -0
- {nameparser-2.5.0 → nameparser-2.6.0}/nameparser/util.py +0 -0
- {nameparser-2.5.0 → nameparser-2.6.0}/nameparser.egg-info/dependency_links.txt +0 -0
- {nameparser-2.5.0 → nameparser-2.6.0}/nameparser.egg-info/requires.txt +0 -0
- {nameparser-2.5.0 → nameparser-2.6.0}/nameparser.egg-info/top_level.txt +0 -0
- {nameparser-2.5.0 → nameparser-2.6.0}/setup.cfg +0 -0
- {nameparser-2.5.0 → nameparser-2.6.0}/tests/__init__.py +0 -0
- {nameparser-2.5.0 → nameparser-2.6.0}/tests/base.py +0 -0
- {nameparser-2.5.0 → nameparser-2.6.0}/tests/test_bound_given_names.py +0 -0
- {nameparser-2.5.0 → nameparser-2.6.0}/tests/test_brute_force.py +0 -0
- {nameparser-2.5.0 → nameparser-2.6.0}/tests/test_comma_variants.py +0 -0
- {nameparser-2.5.0 → nameparser-2.6.0}/tests/test_conjunctions.py +0 -0
- {nameparser-2.5.0 → nameparser-2.6.0}/tests/test_constants.py +0 -0
- {nameparser-2.5.0 → nameparser-2.6.0}/tests/test_east_slavic_patronymic_order.py +0 -0
- {nameparser-2.5.0 → nameparser-2.6.0}/tests/test_first_name.py +0 -0
- {nameparser-2.5.0 → nameparser-2.6.0}/tests/test_middle_name_as_last.py +0 -0
- {nameparser-2.5.0 → nameparser-2.6.0}/tests/test_nicknames.py +0 -0
- {nameparser-2.5.0 → nameparser-2.6.0}/tests/test_output_format.py +0 -0
- {nameparser-2.5.0 → nameparser-2.6.0}/tests/test_python_api.py +0 -0
- {nameparser-2.5.0 → nameparser-2.6.0}/tests/test_titles.py +0 -0
- {nameparser-2.5.0 → nameparser-2.6.0}/tests/test_turkic_patronymic_order.py +0 -0
- {nameparser-2.5.0 → nameparser-2.6.0}/tests/test_variations.py +0 -0
- {nameparser-2.5.0 → nameparser-2.6.0}/tests/v2/__init__.py +0 -0
- {nameparser-2.5.0 → nameparser-2.6.0}/tests/v2/conftest.py +0 -0
- {nameparser-2.5.0 → nameparser-2.6.0}/tests/v2/pipeline/__init__.py +0 -0
- {nameparser-2.5.0 → nameparser-2.6.0}/tests/v2/pipeline/test_assign.py +0 -0
- {nameparser-2.5.0 → nameparser-2.6.0}/tests/v2/pipeline/test_comma.py +0 -0
- {nameparser-2.5.0 → nameparser-2.6.0}/tests/v2/pipeline/test_extract.py +0 -0
- {nameparser-2.5.0 → nameparser-2.6.0}/tests/v2/pipeline/test_segment.py +0 -0
- {nameparser-2.5.0 → nameparser-2.6.0}/tests/v2/pipeline/test_state.py +0 -0
- {nameparser-2.5.0 → nameparser-2.6.0}/tests/v2/test_cldr_agreement.py +0 -0
- {nameparser-2.5.0 → nameparser-2.6.0}/tests/v2/test_cldr_corpus.py +0 -0
- {nameparser-2.5.0 → nameparser-2.6.0}/tests/v2/test_cli.py +0 -0
- {nameparser-2.5.0 → nameparser-2.6.0}/tests/v2/test_config_aliases.py +0 -0
- {nameparser-2.5.0 → nameparser-2.6.0}/tests/v2/test_doc_spellings.py +0 -0
- {nameparser-2.5.0 → nameparser-2.6.0}/tests/v2/test_gnd_corpus.py +0 -0
- {nameparser-2.5.0 → nameparser-2.6.0}/tests/v2/test_layering.py +0 -0
- {nameparser-2.5.0 → nameparser-2.6.0}/tests/v2/test_locale.py +0 -0
- {nameparser-2.5.0 → nameparser-2.6.0}/tests/v2/test_order_correspondence.py +0 -0
- {nameparser-2.5.0 → nameparser-2.6.0}/tests/v2/test_reprs.py +0 -0
- {nameparser-2.5.0 → nameparser-2.6.0}/tests/v2/test_types.py +0 -0
- {nameparser-2.5.0 → nameparser-2.6.0}/tests/v2/test_wikidata_corpus.py +0 -0
- {nameparser-2.5.0 → nameparser-2.6.0}/tests/v2/test_wikidata_native_corpus.py +0 -0
- {nameparser-2.5.0 → nameparser-2.6.0}/tests/v2/test_zh_surnames_tool.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,12 +35,12 @@ 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
|
-
|
|
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
|
|
45
44
|
the details.
|
|
46
45
|
|
|
47
46
|
Chinese, Japanese and Korean names written in their own scripts are read
|
|
@@ -7,12 +7,12 @@ 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
|
-
|
|
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
16
|
the details.
|
|
17
17
|
|
|
18
18
|
Chinese, Japanese and Korean names written in their own scripts are read
|
|
@@ -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
|
|
@@ -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.
|
|
@@ -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/R3: "
|
|
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"),
|
|
@@ -692,6 +690,15 @@ class Lexicon:
|
|
|
692
690
|
#: vocabulary is. Full default list:
|
|
693
691
|
#: :data:`~nameparser.config.suffixes.GLUED_HONORIFICS`.
|
|
694
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()
|
|
695
702
|
#: Lowercase word -> case mask used by capitalized(): the word's
|
|
696
703
|
#: own letters and digits, each in the case it takes ("phd" ->
|
|
697
704
|
#: "PhD"), laid over the word as written, so the one entry
|
|
@@ -739,9 +746,10 @@ class Lexicon:
|
|
|
739
746
|
# the expensive one -- three working configurations broken
|
|
740
747
|
# across two attempts. Do not add a third.
|
|
741
748
|
#
|
|
742
|
-
# The v2 form of
|
|
743
|
-
#
|
|
744
|
-
#
|
|
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.
|
|
745
753
|
contradictory = (
|
|
746
754
|
self.bound_given_names & self.particles) - self.particles_ambiguous
|
|
747
755
|
if contradictory:
|
|
@@ -947,7 +955,8 @@ def _default_lexicon() -> Lexicon:
|
|
|
947
955
|
SUFFIX_WORDS,
|
|
948
956
|
)
|
|
949
957
|
from nameparser.config.surnames import KOREAN_SURNAMES
|
|
950
|
-
from nameparser.config.titles import
|
|
958
|
+
from nameparser.config.titles import (
|
|
959
|
+
AFTER_GIVEN_TITLES, GIVEN_NAME_TITLES, TITLES)
|
|
951
960
|
|
|
952
961
|
# every vocabulary constant is a frozenset since #293, so each one
|
|
953
962
|
# feeds its strictly-typed frozenset[str] field as it stands -- and
|
|
@@ -975,6 +984,7 @@ def _default_lexicon() -> Lexicon:
|
|
|
975
984
|
maiden_markers=MAIDEN_MARKERS,
|
|
976
985
|
surnames=KOREAN_SURNAMES,
|
|
977
986
|
honorific_tails=GLUED_HONORIFICS,
|
|
987
|
+
after_given_titles=AFTER_GIVEN_TITLES,
|
|
978
988
|
# pass canonical pair-tuples so this strictly-typed call site never
|
|
979
989
|
# feeds a Mapping to the tuple-annotated field; __post_init__
|
|
980
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/A1's Accepted
|
|
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,7 +215,7 @@ 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
|
|
@@ -3,7 +3,7 @@
|
|
|
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
5
|
#626), and segments, for the part a withdrawn connective report's
|
|
6
|
-
letter ends (rules/
|
|
6
|
+
letter ends (rules/P8, #721).
|
|
7
7
|
Produces: a validated ParsedName -- the constructor re-checks every
|
|
8
8
|
invariant (span order/bounds, ambiguity subset), so a pipeline bug
|
|
9
9
|
that would produce an invalid result dies HERE, not in a renderer
|
|
@@ -116,7 +116,7 @@ def assemble(state: ParseState) -> ParsedName:
|
|
|
116
116
|
and any(t.role in (Role.SUFFIX, Role.TITLE)
|
|
117
117
|
for t in materialized)):
|
|
118
118
|
continue
|
|
119
|
-
# rules/
|
|
119
|
+
# rules/P8: "unless no name word of its part stands to its
|
|
120
120
|
# right" -- nothing after the letter in its segment, or only
|
|
121
121
|
# suffixes, titles, a maiden clause or a nickname: it connects
|
|
122
122
|
# nothing, so the report goes (#721). Asked here for the reason
|
|
@@ -15,7 +15,7 @@ in the Han/Hiragana/Katakana repertoire the #272 kana license shares
|
|
|
15
15
|
across pieces); token/piece tags; Lexicon only through tags already
|
|
16
16
|
applied by classify (plus the leading-title period rule).
|
|
17
17
|
|
|
18
|
-
Implements rules H2, H4, H5, H6, N3, O4, O5, S10, W4, and P6 after a family comma,
|
|
18
|
+
Implements rules H2, H4, H5, H6, H7, H8, N3, O4, O5, S10, W4, and P6 after a family comma,
|
|
19
19
|
of docs/design/rules/,
|
|
20
20
|
each cited at its code below. Ports v1's assignment loops.
|
|
21
21
|
NO_COMMA (per name_order):
|
|
@@ -78,8 +78,8 @@ group's head: the comma's own decision, #544's anchored members and
|
|
|
78
78
|
not counted here. And
|
|
79
79
|
at the one site that places a LONE name word, GIVEN_OR_FAMILY for the
|
|
80
80
|
field the convention picked (O5, #449) and TITLE_OR_NAME for the two
|
|
81
|
-
shapes where the doubt is whether a word is a title instead (H4
|
|
82
|
-
#491): the peel leaving one title-vocabulary word standing, and a
|
|
81
|
+
shapes where the doubt is whether a word is a title instead (H4 and
|
|
82
|
+
H7, #491): the peel leaving one title-vocabulary word standing, and a
|
|
83
83
|
joined unit carrying title vocabulary.
|
|
84
84
|
"""
|
|
85
85
|
from __future__ import annotations
|
|
@@ -339,8 +339,8 @@ def _assign_main(seg_idx: int, state: ParseState,
|
|
|
339
339
|
# reads that word as the family name" (history: decisions/N3)
|
|
340
340
|
# -- v1's p_len == 1 counted
|
|
341
341
|
# the WHOLE segment before any title peeling -- 'Xyz. (Bud) Smith'
|
|
342
|
-
# has two pieces, so the title peel wins and
|
|
343
|
-
#
|
|
342
|
+
# has two pieces, so the title peel wins and this rule declines;
|
|
343
|
+
# H1 then names Smith the family (rules/N3's title bullet)
|
|
344
344
|
# The nickname scan sits LAST in the test: it is a generator, which
|
|
345
345
|
# every interpreter resumes once per token (seven call events on
|
|
346
346
|
# the reference name), and only a one-piece segment ever reads its
|
|
@@ -478,18 +478,20 @@ def _assign_main(seg_idx: int, state: ParseState,
|
|
|
478
478
|
# Jr.' and "'Smitty' Jones Jr." ARE this convention -- and the
|
|
479
479
|
# count comes off the peel rather than off name_pieces, which
|
|
480
480
|
# is what keeps the carve-out above out of these branches.
|
|
481
|
-
# Title vocabulary in the unit makes the doubt H4's (is
|
|
482
|
-
# WORD a title), anything else O5's (which FIELD it took),
|
|
483
|
-
# only the second reads `field_undecided`.
|
|
481
|
+
# Title vocabulary in the unit makes the doubt H4's or H7's (is
|
|
482
|
+
# the WORD a title), anything else O5's (which FIELD it took),
|
|
483
|
+
# so only the second reads `field_undecided`.
|
|
484
484
|
if peeled.names == 1 and not resolved.by_script:
|
|
485
485
|
# rules/H4: "an input whose only remaining name word
|
|
486
486
|
# after the title peel is itself title vocabulary reads
|
|
487
487
|
# that word as the name by convention and reports
|
|
488
|
-
# `title-or-name`" (history: decisions/H4), and
|
|
489
|
-
#
|
|
490
|
-
#
|
|
491
|
-
#
|
|
492
|
-
#
|
|
488
|
+
# `title-or-name`" (history: decisions/H4), and
|
|
489
|
+
# rules/H7: "one name UNIT of more than one word carrying
|
|
490
|
+
# title vocabulary reports `title-or-name`" (history:
|
|
491
|
+
# decisions/H7, decisions/H4 #518) -- one branch, its detail
|
|
492
|
+
# naming a field only in H7's shape ('John of Prince',
|
|
493
|
+
# 'Attorney General of Minnesota'), where the unit is more
|
|
494
|
+
# than the title word.
|
|
493
495
|
# A LONE title word never reaches here ('Dr.', 'Prince of
|
|
494
496
|
# Wales'): the leading-title peel took the whole name.
|
|
495
497
|
if any("vocab:title" in tokens[i].tags for i in head):
|
|
@@ -635,7 +637,7 @@ def _inside_a_credential_run(seg: Sequence[Sequence[int]],
|
|
|
635
637
|
tokens: list[WorkToken], given_at: int,
|
|
636
638
|
k: int, end: int,
|
|
637
639
|
ambiguities: list[PendingAmbiguity]) -> bool:
|
|
638
|
-
"""rules/P6's third exception for the run `seg[k:end]`: a
|
|
640
|
+
"""rules/P9 (P6's third exception) for the run `seg[k:end]`: a
|
|
639
641
|
run assign read as post-nominals, with a credential read in
|
|
640
642
|
front of it and another behind, stands INSIDE the credential run
|
|
641
643
|
assign read whole rather than ending the name, so it keeps that
|
|
@@ -1257,7 +1259,11 @@ def assign(state: ParseState) -> ParseState:
|
|
|
1257
1259
|
candidates = [k for k in range(n, len(pieces))
|
|
1258
1260
|
if k == n
|
|
1259
1261
|
or not reads_as_a_suffix(k, frozenset())]
|
|
1260
|
-
|
|
1262
|
+
# rules/H8: "the word joins the title field, and the given
|
|
1263
|
+
# part reads as it would without it" -- a bare salutation
|
|
1264
|
+
# chains here as a period-marked title does
|
|
1265
|
+
kept = trailing_titles(candidates, pieces, ptags, tokens,
|
|
1266
|
+
salutations=True)
|
|
1261
1267
|
walkable = frozenset(candidates)
|
|
1262
1268
|
titled_idx = frozenset(candidates[kept:])
|
|
1263
1269
|
for k in titled_idx:
|
|
@@ -1459,8 +1465,8 @@ def assign(state: ParseState) -> ParseState:
|
|
|
1459
1465
|
# Berg'), or where everything behind it is post-nominal, which
|
|
1460
1466
|
# leaves no family word to address -- H3's concern, by a
|
|
1461
1467
|
# stricter test than H3's one-word give-back, so this is not
|
|
1462
|
-
# `leading_titles` ('
|
|
1463
|
-
# '
|
|
1468
|
+
# `leading_titles` ('RA PhD Jr., John' keeps family 'RA', where
|
|
1469
|
+
# 'RA PhD Jr. John' reads title 'RA'). A dual is a title wherever
|
|
1464
1470
|
# a name word follows it ('Rev. Sr. Smith, Mary').
|
|
1465
1471
|
# `is_title_piece` is H5's vocabulary read, so the two slots that
|
|
1466
1472
|
# refuse the shape agree on what a title word is.
|
|
@@ -1487,10 +1493,10 @@ def assign(state: ParseState) -> ParseState:
|
|
|
1487
1493
|
and "particle" not in tokens[fam_pieces[lead][0]].tags):
|
|
1488
1494
|
_set_roles(tokens, fam_pieces[lead], Role.TITLE)
|
|
1489
1495
|
lead += 1
|
|
1490
|
-
# rules/
|
|
1491
|
-
# name
|
|
1492
|
-
# (vd, mc)
|
|
1493
|
-
#
|
|
1496
|
+
# rules/P9: "a particle that is also suffix vocabulary heads
|
|
1497
|
+
# the name word behind it" -- with a name piece behind it in
|
|
1498
|
+
# this part (vd, mc), it heads that name, as P2's join does,
|
|
1499
|
+
# and is not peeled from between two family words
|
|
1494
1500
|
# (#573: 'SMITH VD MA, JOHN', where the uniform case left the
|
|
1495
1501
|
# chain stopped before VD; 'SMITH VD JR, JOHN', a suffix word
|
|
1496
1502
|
# behind it, keeps suffix 'VD JR'). Read here because only
|
|
@@ -17,7 +17,11 @@ honorific_tails, which script_segment consumes upstream; and, since
|
|
|
17
17
|
which decide whether an UNLISTED dotted or all-caps token joins the
|
|
18
18
|
ambiguous credential class by SHAPE (#516) -- the all-caps half here
|
|
19
19
|
only under CapsSuffixes.EVERYWHERE, the default's part after a comma
|
|
20
|
-
being tagged by `_comma.decide` when group binds it (#613)
|
|
20
|
+
being tagged by `_comma.decide` when group binds it (#613); and
|
|
21
|
+
Policy.extra_suffix_delimiters, with the built-in semicolon
|
|
22
|
+
(_vocab.delimiter_cores), so a word after the first comma with a
|
|
23
|
+
delimiter glued to it is tagged as the word without it (rules/C10).
|
|
24
|
+
is_initial also consults
|
|
21
25
|
the _policy module's _NO_INITIALS constant. It is named apart from the
|
|
22
26
|
fields above because it is not CONFIGURATION -- no Lexicon or Policy
|
|
23
27
|
carries it and no caller can change it -- and not because it decides
|
|
@@ -26,6 +30,7 @@ nothing: the tags this stage writes do vary by it ('씨.' is not tagged
|
|
|
26
30
|
|
|
27
31
|
Tags emitted -- stable (API): "particle", "conjunction", "initial";
|
|
28
32
|
namespaced (unstable): "vocab:title", "vocab:given-title",
|
|
33
|
+
"vocab:after-given-title",
|
|
29
34
|
"vocab:suffix", "vocab:suffix-word", "vocab:suffix-ambiguous",
|
|
30
35
|
"vocab:particle-ambiguous", "vocab:bound-given", "vocab:maiden-marker",
|
|
31
36
|
"vocab:maiden-marker-cont"; and, in a namespace of its own,
|
|
@@ -62,7 +67,8 @@ from nameparser._pipeline._state import (
|
|
|
62
67
|
)
|
|
63
68
|
from nameparser._types import AmbiguityKind, Role
|
|
64
69
|
from nameparser._pipeline._vocab import (
|
|
65
|
-
_INITIAL, caps_shape_candidate, composed_len,
|
|
70
|
+
BUILTIN_SUFFIX_DELIMITERS, _INITIAL, caps_shape_candidate, composed_len,
|
|
71
|
+
delimiter_cores, glued_stem, is_initial, is_one_case, is_paired_initials,
|
|
66
72
|
period_joined_vocab, suffix_as_written, tag_marker_runs,
|
|
67
73
|
)
|
|
68
74
|
from nameparser._pipeline._pieces import own_words
|
|
@@ -71,11 +77,11 @@ from nameparser._pipeline._pieces import own_words
|
|
|
71
77
|
|
|
72
78
|
|
|
73
79
|
# rules/S2: "a trailing word of the suffix vocabulary reads as a
|
|
74
|
-
# suffix — generational forms and credential acronyms alike
|
|
75
|
-
# ambiguous acronym written with its periods, one after
|
|
76
|
-
# letter or one after each of two or more letter chunks, counts
|
|
80
|
+
# suffix — generational forms and credential acronyms alike" -- and
|
|
81
|
+
# rules/S3: "an ambiguous acronym written with its periods, one after
|
|
82
|
+
# each letter or one after each of two or more letter chunks, counts
|
|
77
83
|
# unambiguously; a single trailing period is the abbreviation shape
|
|
78
|
-
# any word can wear and does not" -- and: "a
|
|
84
|
+
# any word can wear and does not" -- and rules/S4: "a
|
|
79
85
|
# bare ambiguous acronym is consumed only when the name has words to
|
|
80
86
|
# spare"
|
|
81
87
|
def _tags_for(token: WorkToken, n: str, state: ParseState,
|
|
@@ -123,6 +129,10 @@ def _tags_for(token: WorkToken, n: str, state: ParseState,
|
|
|
123
129
|
tags.add("vocab:title")
|
|
124
130
|
if n in lex.given_name_titles:
|
|
125
131
|
tags.add("vocab:given-title")
|
|
132
|
+
# rules/H8: "the word joins the title field" -- tagged only where the
|
|
133
|
+
# word is a title too, so an entry outside `titles` is inert
|
|
134
|
+
if n in lex.after_given_titles and n in lex.titles:
|
|
135
|
+
tags.add("vocab:after-given-title")
|
|
126
136
|
if suffix_as_written(n, token.text, lex):
|
|
127
137
|
tags.add("vocab:suffix")
|
|
128
138
|
if n in lex.suffix_words:
|
|
@@ -136,12 +146,12 @@ def _tags_for(token: WorkToken, n: str, state: ParseState,
|
|
|
136
146
|
tags.add("particle")
|
|
137
147
|
if n in lex.particles_ambiguous:
|
|
138
148
|
tags.add("vocab:particle-ambiguous")
|
|
139
|
-
# rules/
|
|
149
|
+
# rules/P8: "a single-letter connective reads as an initial
|
|
140
150
|
# where the writing says so: written as a bare Latin capital in a
|
|
141
151
|
# name that is not written wholly in one case, or — in a name
|
|
142
152
|
# written wholly in one case, where nothing says so — where the
|
|
143
153
|
# letter is one the vocabulary marks as reading both ways"
|
|
144
|
-
# (#383/#479; history: decisions/
|
|
154
|
+
# (#383/#479; history: decisions/P8)
|
|
145
155
|
# one letter as the writer sees it: a decomposed 'й' is two code
|
|
146
156
|
# points (#665), so non-ASCII text is counted composed, after the
|
|
147
157
|
# C-level tests have declined everything else
|
|
@@ -204,8 +214,8 @@ def _tags_for(token: WorkToken, n: str, state: ParseState,
|
|
|
204
214
|
# title as a whole ('Lt.Gov.', and by the ANY rule 'Mr.Smith');
|
|
205
215
|
# else ANY suffix chunk makes it a suffix ('JD.CPA'). Title wins
|
|
206
216
|
# (v1's continue). The third and fourth are 2.4's by-shape halves
|
|
207
|
-
# (#516; rules/S9: "
|
|
208
|
-
#
|
|
217
|
+
# (#516; rules/S9: "joins the ambiguous class (S4) by SHAPE, in
|
|
218
|
+
# the positions each shape is admitted") and reach only a
|
|
209
219
|
# token NO vocabulary claimed: a dotted token of two or more
|
|
210
220
|
# alphabetic chunks, and -- where its switch is on -- an unlisted
|
|
211
221
|
# all-caps word. They are an `elif` chain
|
|
@@ -255,7 +265,16 @@ def _tags_for(token: WorkToken, n: str, state: ParseState,
|
|
|
255
265
|
# shipped ambiguous vocabulary carries no periods, so
|
|
256
266
|
# nothing default changes.
|
|
257
267
|
tags.add(SHAPE_ACRONYM_TAG)
|
|
258
|
-
|
|
268
|
+
# rules/S3: "Paired initials are the exception, as C5 defines
|
|
269
|
+
# them, in a script that writes capitals" -- the shape
|
|
270
|
+
# tag alone, as with the switch off, so the trailing slots
|
|
271
|
+
# read them as a name word and report the fork; the comma
|
|
272
|
+
# restores the class tag where C5 reads them
|
|
273
|
+
# (decisions/S3 (#864)). Cased: a text with no case mapping
|
|
274
|
+
# is in a script without capitals ('พ.บ.').
|
|
275
|
+
if (state.policy.unlisted_dotted_suffixes
|
|
276
|
+
and not (is_paired_initials(token.text)
|
|
277
|
+
and token.text.upper() != token.text.lower())):
|
|
259
278
|
tags.add(AMBIGUOUS_ACRONYM_TAG)
|
|
260
279
|
elif (state.policy.unlisted_caps_suffixes is CapsSuffixes.EVERYWHERE
|
|
261
280
|
and token.role is None
|
|
@@ -289,6 +308,16 @@ def _tags_for(token: WorkToken, n: str, state: ParseState,
|
|
|
289
308
|
return frozenset(tags)
|
|
290
309
|
|
|
291
310
|
|
|
311
|
+
#: The tags of a word that joins the word after it, which a delimiter
|
|
312
|
+
#: glued to the word stands between (rules/C10). A particle's are among
|
|
313
|
+
#: them though it also attaches backward (rules/P6): kept, it chained
|
|
314
|
+
#: forward across the delimiter ('Berg, de; Smith' read family 'Berg de;
|
|
315
|
+
#: Smith'), and blocking the forward joins alone would guard every site
|
|
316
|
+
#: that reads the tag (decisions/C10).
|
|
317
|
+
_JOINING_TAGS = frozenset({"particle", "conjunction",
|
|
318
|
+
"vocab:particle-ambiguous", "vocab:bound-given"})
|
|
319
|
+
|
|
320
|
+
|
|
292
321
|
def classify(state: ParseState) -> ParseState:
|
|
293
322
|
# One fold per token, shared by the marker pass and the vocabulary
|
|
294
323
|
# tags -- the shape suffix_as_written already asks for ("n is
|
|
@@ -325,15 +354,47 @@ def classify(state: ParseState) -> ParseState:
|
|
|
325
354
|
whole_one_case = one_case and (
|
|
326
355
|
len(own) == len(state.tokens)
|
|
327
356
|
or is_one_case([t.text for t in state.tokens]))
|
|
357
|
+
# rules/C10: "the word keeps the delimiter in its text and reads as
|
|
358
|
+
# it would without it" -- tagged as the word without it ('MD;' as
|
|
359
|
+
# 'MD'), so every reading that asks a word's vocabulary asks the
|
|
360
|
+
# stem's; only where the delimiter "stands after the first comma",
|
|
361
|
+
# a semicolon before it being a word (decisions/C10, D15)
|
|
362
|
+
readers = list(state.tokens)
|
|
363
|
+
stemmed: set[int] = set()
|
|
364
|
+
if state.comma_offsets:
|
|
365
|
+
policy_cores = state.policy.extra_suffix_delimiters
|
|
366
|
+
cores = (BUILTIN_SUFFIX_DELIMITERS if not policy_cores
|
|
367
|
+
else delimiter_cores(policy_cores))
|
|
368
|
+
first = state.comma_offsets[0]
|
|
369
|
+
for i, t in enumerate(state.tokens):
|
|
370
|
+
text = t.text
|
|
371
|
+
if t.span.start < first:
|
|
372
|
+
continue
|
|
373
|
+
for core in cores:
|
|
374
|
+
if core in text:
|
|
375
|
+
stem = glued_stem(text, cores)
|
|
376
|
+
if stem is not None:
|
|
377
|
+
readers[i] = copy_with(t, text=stem)
|
|
378
|
+
folded[i] = _normalize(stem)
|
|
379
|
+
stemmed.add(i)
|
|
380
|
+
break
|
|
328
381
|
tokens = tuple(
|
|
329
382
|
copy_with(
|
|
330
|
-
t, tags=_tags_for(
|
|
383
|
+
t, tags=_tags_for(readers[i], folded[i], state, marker_tags.get(i),
|
|
331
384
|
one_case_own=one_case and i < clause_at
|
|
332
385
|
and t.role is None, one_case=one_case,
|
|
333
386
|
letter_one_case=whole_one_case or (
|
|
334
387
|
one_case and i < clause_at
|
|
335
388
|
and t.role is None)))
|
|
336
389
|
for i, t in enumerate(state.tokens))
|
|
390
|
+
# A word with a delimiter glued to it joins no neighbour across the
|
|
391
|
+
# delimiter, so the stem's joining tags stay off it: 'y;' is no
|
|
392
|
+
# connective to the word after it (rules/C10: "no join, maiden
|
|
393
|
+
# clause or connective reaches across it")
|
|
394
|
+
if stemmed:
|
|
395
|
+
tokens = tuple(
|
|
396
|
+
copy_with(t, tags=t.tags - _JOINING_TAGS) if i in stemmed else t
|
|
397
|
+
for i, t in enumerate(tokens))
|
|
337
398
|
# Delimited content whose vocabulary cannot settle it: extract's
|
|
338
399
|
# escape sends an UNambiguous suffix straight through ("(MBA)" ->
|
|
339
400
|
# suffix) and keeps everything else as a nickname, so an AMBIGUOUS
|
|
@@ -372,14 +433,14 @@ def classify(state: ParseState) -> ParseState:
|
|
|
372
433
|
# hands classify tokens it did not build; no such path exists
|
|
373
434
|
# today. `conjunctions` is the clause doing real work: it keeps
|
|
374
435
|
# an orphan marker (a conjunctions_ambiguous entry no longer in
|
|
375
|
-
# conjunctions) inert rather than reported (decisions/
|
|
436
|
+
# conjunctions) inert rather than reported (decisions/P8).
|
|
376
437
|
# `i < clause_at and token.role is None` mirrors the fork's own
|
|
377
438
|
# "own words" test above, for the same reason (rules/P3): a
|
|
378
439
|
# clause's words were never eligible for the fork, so they must
|
|
379
440
|
# never be eligible to report either. Emitted at the decision
|
|
380
441
|
# site (mechanisms.md#AMBIGUITY-AT-THE-DECISION-SITE), per
|
|
381
442
|
# token -- 'e and e e' emits twice; assemble withdraws the
|
|
382
|
-
# report on a letter no name word follows (rules/
|
|
443
|
+
# report on a letter no name word follows (rules/P8, #721).
|
|
383
444
|
if (one_case and "initial" in token.tags
|
|
384
445
|
and folded[i] in state.lexicon.conjunctions_ambiguous
|
|
385
446
|
and folded[i] in state.lexicon.conjunctions
|