nameparser 1.3.0__tar.gz → 1.3.1__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-1.3.0/nameparser.egg-info → nameparser-1.3.1}/PKG-INFO +37 -23
- {nameparser-1.3.0 → nameparser-1.3.1}/README.rst +36 -22
- {nameparser-1.3.0 → nameparser-1.3.1}/nameparser/_version.py +1 -1
- {nameparser-1.3.0 → nameparser-1.3.1}/nameparser/config/regexes.py +7 -0
- {nameparser-1.3.0 → nameparser-1.3.1}/nameparser/parser.py +20 -3
- {nameparser-1.3.0 → nameparser-1.3.1/nameparser.egg-info}/PKG-INFO +37 -23
- {nameparser-1.3.0 → nameparser-1.3.1}/pyproject.toml +1 -0
- {nameparser-1.3.0 → nameparser-1.3.1}/tests/test_output_format.py +39 -0
- {nameparser-1.3.0 → nameparser-1.3.1}/AUTHORS +0 -0
- {nameparser-1.3.0 → nameparser-1.3.1}/LICENSE +0 -0
- {nameparser-1.3.0 → nameparser-1.3.1}/MANIFEST.in +0 -0
- {nameparser-1.3.0 → nameparser-1.3.1}/nameparser/__init__.py +0 -0
- {nameparser-1.3.0 → nameparser-1.3.1}/nameparser/__main__.py +0 -0
- {nameparser-1.3.0 → nameparser-1.3.1}/nameparser/config/__init__.py +0 -0
- {nameparser-1.3.0 → nameparser-1.3.1}/nameparser/config/bound_first_names.py +0 -0
- {nameparser-1.3.0 → nameparser-1.3.1}/nameparser/config/capitalization.py +0 -0
- {nameparser-1.3.0 → nameparser-1.3.1}/nameparser/config/conjunctions.py +0 -0
- {nameparser-1.3.0 → nameparser-1.3.1}/nameparser/config/prefixes.py +0 -0
- {nameparser-1.3.0 → nameparser-1.3.1}/nameparser/config/suffixes.py +0 -0
- {nameparser-1.3.0 → nameparser-1.3.1}/nameparser/config/titles.py +0 -0
- {nameparser-1.3.0 → nameparser-1.3.1}/nameparser/py.typed +0 -0
- {nameparser-1.3.0 → nameparser-1.3.1}/nameparser/util.py +0 -0
- {nameparser-1.3.0 → nameparser-1.3.1}/nameparser.egg-info/SOURCES.txt +0 -0
- {nameparser-1.3.0 → nameparser-1.3.1}/nameparser.egg-info/dependency_links.txt +0 -0
- {nameparser-1.3.0 → nameparser-1.3.1}/nameparser.egg-info/requires.txt +0 -0
- {nameparser-1.3.0 → nameparser-1.3.1}/nameparser.egg-info/top_level.txt +0 -0
- {nameparser-1.3.0 → nameparser-1.3.1}/setup.cfg +0 -0
- {nameparser-1.3.0 → nameparser-1.3.1}/tests/__init__.py +0 -0
- {nameparser-1.3.0 → nameparser-1.3.1}/tests/base.py +0 -0
- {nameparser-1.3.0 → nameparser-1.3.1}/tests/conftest.py +0 -0
- {nameparser-1.3.0 → nameparser-1.3.1}/tests/test_bound_first_names.py +0 -0
- {nameparser-1.3.0 → nameparser-1.3.1}/tests/test_brute_force.py +0 -0
- {nameparser-1.3.0 → nameparser-1.3.1}/tests/test_capitalization.py +0 -0
- {nameparser-1.3.0 → nameparser-1.3.1}/tests/test_config_attribute_docstrings.py +0 -0
- {nameparser-1.3.0 → nameparser-1.3.1}/tests/test_conjunctions.py +0 -0
- {nameparser-1.3.0 → nameparser-1.3.1}/tests/test_constants.py +0 -0
- {nameparser-1.3.0 → nameparser-1.3.1}/tests/test_east_slavic_patronymic_order.py +0 -0
- {nameparser-1.3.0 → nameparser-1.3.1}/tests/test_first_name.py +0 -0
- {nameparser-1.3.0 → nameparser-1.3.1}/tests/test_initials.py +0 -0
- {nameparser-1.3.0 → nameparser-1.3.1}/tests/test_middle_name_as_last.py +0 -0
- {nameparser-1.3.0 → nameparser-1.3.1}/tests/test_nicknames.py +0 -0
- {nameparser-1.3.0 → nameparser-1.3.1}/tests/test_parser_util.py +0 -0
- {nameparser-1.3.0 → nameparser-1.3.1}/tests/test_prefixes.py +0 -0
- {nameparser-1.3.0 → nameparser-1.3.1}/tests/test_python_api.py +0 -0
- {nameparser-1.3.0 → nameparser-1.3.1}/tests/test_suffixes.py +0 -0
- {nameparser-1.3.0 → nameparser-1.3.1}/tests/test_titles.py +0 -0
- {nameparser-1.3.0 → nameparser-1.3.1}/tests/test_turkic_patronymic_order.py +0 -0
- {nameparser-1.3.0 → nameparser-1.3.1}/tests/test_variations.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nameparser
|
|
3
|
-
Version: 1.3.
|
|
3
|
+
Version: 1.3.1
|
|
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
6
|
License: LGPL
|
|
@@ -57,19 +57,35 @@ are optional. Comma-separated format like "Last, First" is also supported.
|
|
|
57
57
|
2. Lastname [Suffix], Title Firstname (Nickname) Middle Middle[,] Suffix [, Suffix]
|
|
58
58
|
3. Title Firstname M Lastname [Suffix], Suffix [Suffix] [, Suffix]
|
|
59
59
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
60
|
+
How It Works
|
|
61
|
+
~~~~~~~~~~~~
|
|
62
|
+
|
|
63
|
+
The parser works in two layers.
|
|
64
|
+
|
|
65
|
+
A **vocabulary layer** recognizes name pieces by what they are, using
|
|
66
|
+
configurable sets of known words: titles ("Dr."), suffixes ("III", "PhD"),
|
|
67
|
+
last-name prefixes ("de la"), conjunctions ("y", "&"), and delimited
|
|
68
|
+
nicknames ("Doc"). Titles and conjunctions chain together to handle complex
|
|
69
|
+
titles like "Asst Secretary of State"; prefixes join forward so "de la Vega"
|
|
70
|
+
stays one last name. This layer doesn't care where in the string a word
|
|
71
|
+
appears — and it's the layer you customize, by adding or removing entries
|
|
72
|
+
in the sets to fit your dataset.
|
|
73
|
+
|
|
74
|
+
A **positional layer** then assigns everything the vocabulary layer didn't
|
|
75
|
+
claim, based purely on where it sits: the first unclaimed word is the first
|
|
76
|
+
name, the last one is the last name, and anything between them is a middle
|
|
77
|
+
name. There is no semantic understanding — "Dr" is a title when it comes
|
|
78
|
+
before a name and a suffix when it comes after ("pre-nominal" and
|
|
79
|
+
"post-nominal" would probably be better names) — and no attempt to correct
|
|
80
|
+
mistakes in the input.
|
|
81
|
+
|
|
82
|
+
It attempts the best guess that can be made with a simple, deterministic,
|
|
83
|
+
rule-based approach — no statistical models or machine learning; the same
|
|
84
|
+
input always parses the same way. The positional layer assumes Western name
|
|
85
|
+
order (given name first), so the main use case is English and other
|
|
86
|
+
languages that share that structure. It can also try to correct the
|
|
87
|
+
capitalization of names that are all upper- or lowercase. It's not perfect,
|
|
88
|
+
but it gets you pretty far.
|
|
73
89
|
|
|
74
90
|
Installation
|
|
75
91
|
------------
|
|
@@ -116,11 +132,8 @@ Quick Start Example
|
|
|
116
132
|
'Juan de la Vega'
|
|
117
133
|
|
|
118
134
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
the difference between 'title' and 'suffix' is positional, not semantic. "Dr" is a title
|
|
122
|
-
when it comes before the name and a suffix when it comes after. ("Pre-nominal"
|
|
123
|
-
and "post-nominal" would probably be better names.)
|
|
135
|
+
Because the positional layer has no semantic understanding, position is
|
|
136
|
+
everything:
|
|
124
137
|
|
|
125
138
|
::
|
|
126
139
|
|
|
@@ -139,10 +152,11 @@ and "post-nominal" would probably be better names.)
|
|
|
139
152
|
Customization
|
|
140
153
|
-------------
|
|
141
154
|
|
|
142
|
-
Your project may need some adjustment for your dataset.
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
155
|
+
Your project may need some adjustment for your dataset. Most customization
|
|
156
|
+
is vocabulary — `customizing the configured pre-defined sets`_ of titles,
|
|
157
|
+
prefixes, etc. that the vocabulary layer matches against. You can also do
|
|
158
|
+
your own pre- or post-processing, or subclass the `HumanName` class for
|
|
159
|
+
deeper changes. See the `full documentation`_ for more information.
|
|
146
160
|
|
|
147
161
|
|
|
148
162
|
`Full documentation`_
|
|
@@ -29,19 +29,35 @@ are optional. Comma-separated format like "Last, First" is also supported.
|
|
|
29
29
|
2. Lastname [Suffix], Title Firstname (Nickname) Middle Middle[,] Suffix [, Suffix]
|
|
30
30
|
3. Title Firstname M Lastname [Suffix], Suffix [Suffix] [, Suffix]
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
32
|
+
How It Works
|
|
33
|
+
~~~~~~~~~~~~
|
|
34
|
+
|
|
35
|
+
The parser works in two layers.
|
|
36
|
+
|
|
37
|
+
A **vocabulary layer** recognizes name pieces by what they are, using
|
|
38
|
+
configurable sets of known words: titles ("Dr."), suffixes ("III", "PhD"),
|
|
39
|
+
last-name prefixes ("de la"), conjunctions ("y", "&"), and delimited
|
|
40
|
+
nicknames ("Doc"). Titles and conjunctions chain together to handle complex
|
|
41
|
+
titles like "Asst Secretary of State"; prefixes join forward so "de la Vega"
|
|
42
|
+
stays one last name. This layer doesn't care where in the string a word
|
|
43
|
+
appears — and it's the layer you customize, by adding or removing entries
|
|
44
|
+
in the sets to fit your dataset.
|
|
45
|
+
|
|
46
|
+
A **positional layer** then assigns everything the vocabulary layer didn't
|
|
47
|
+
claim, based purely on where it sits: the first unclaimed word is the first
|
|
48
|
+
name, the last one is the last name, and anything between them is a middle
|
|
49
|
+
name. There is no semantic understanding — "Dr" is a title when it comes
|
|
50
|
+
before a name and a suffix when it comes after ("pre-nominal" and
|
|
51
|
+
"post-nominal" would probably be better names) — and no attempt to correct
|
|
52
|
+
mistakes in the input.
|
|
53
|
+
|
|
54
|
+
It attempts the best guess that can be made with a simple, deterministic,
|
|
55
|
+
rule-based approach — no statistical models or machine learning; the same
|
|
56
|
+
input always parses the same way. The positional layer assumes Western name
|
|
57
|
+
order (given name first), so the main use case is English and other
|
|
58
|
+
languages that share that structure. It can also try to correct the
|
|
59
|
+
capitalization of names that are all upper- or lowercase. It's not perfect,
|
|
60
|
+
but it gets you pretty far.
|
|
45
61
|
|
|
46
62
|
Installation
|
|
47
63
|
------------
|
|
@@ -88,11 +104,8 @@ Quick Start Example
|
|
|
88
104
|
'Juan de la Vega'
|
|
89
105
|
|
|
90
106
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
the difference between 'title' and 'suffix' is positional, not semantic. "Dr" is a title
|
|
94
|
-
when it comes before the name and a suffix when it comes after. ("Pre-nominal"
|
|
95
|
-
and "post-nominal" would probably be better names.)
|
|
107
|
+
Because the positional layer has no semantic understanding, position is
|
|
108
|
+
everything:
|
|
96
109
|
|
|
97
110
|
::
|
|
98
111
|
|
|
@@ -111,10 +124,11 @@ and "post-nominal" would probably be better names.)
|
|
|
111
124
|
Customization
|
|
112
125
|
-------------
|
|
113
126
|
|
|
114
|
-
Your project may need some adjustment for your dataset.
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
127
|
+
Your project may need some adjustment for your dataset. Most customization
|
|
128
|
+
is vocabulary — `customizing the configured pre-defined sets`_ of titles,
|
|
129
|
+
prefixes, etc. that the vocabulary layer matches against. You can also do
|
|
130
|
+
your own pre- or post-processing, or subclass the `HumanName` class for
|
|
131
|
+
deeper changes. See the `full documentation`_ for more information.
|
|
118
132
|
|
|
119
133
|
|
|
120
134
|
`Full documentation`_
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
VERSION = (1, 3,
|
|
1
|
+
VERSION = (1, 3, 1)
|
|
2
2
|
__version__ = '.'.join(map(str, VERSION))
|
|
@@ -6,6 +6,12 @@ re_emoji = re.compile('[' # lgtm[py/overly-large-range]
|
|
|
6
6
|
'\U0001F680-\U0001F6FF'
|
|
7
7
|
'\u2600-\u26FF\u2700-\u27BF]+')
|
|
8
8
|
|
|
9
|
+
# Invisible bidirectional formatting characters: ALM, LRM, RLM, the
|
|
10
|
+
# embedding/override marks (LRE/RLE/PDF/LRO/RLO) and the isolates
|
|
11
|
+
# (LRI/RLI/FSI/PDI). Copy-pasted RTL names often carry these; they render
|
|
12
|
+
# as nothing but stick to name parts and break equality/lookups.
|
|
13
|
+
re_bidi = re.compile('[\u061C\u200E\u200F\u202A-\u202E\u2066-\u2069]+')
|
|
14
|
+
|
|
9
15
|
EMPTY_REGEX = re.compile('')
|
|
10
16
|
|
|
11
17
|
REGEXES = {
|
|
@@ -20,6 +26,7 @@ REGEXES = {
|
|
|
20
26
|
"no_vowels": re.compile(r'^[^aeyiuo]+$', re.I),
|
|
21
27
|
"period_not_at_end": re.compile(r'.*\..+$', re.I),
|
|
22
28
|
"emoji": re_emoji,
|
|
29
|
+
"bidi": re_bidi,
|
|
23
30
|
"phd": re.compile(r'\s(ph\.?\s+d\.?)', re.I),
|
|
24
31
|
"space_before_comma": re.compile(r'\s+,'),
|
|
25
32
|
"east_slavic_patronymic": re.compile(
|
|
@@ -241,9 +241,14 @@ class HumanName:
|
|
|
241
241
|
def __str__(self) -> str:
|
|
242
242
|
if self.string_format is not None:
|
|
243
243
|
# string_format = "{title} {first} {middle} {last} {suffix} ({nickname})"
|
|
244
|
-
|
|
244
|
+
# Empty attributes must render as '' (not empty_attribute_default,
|
|
245
|
+
# which may be None) so str.format does not interpolate the
|
|
246
|
+
# literal "None" into the output, which cannot be scrubbed
|
|
247
|
+
# afterward without corrupting name text containing the same
|
|
248
|
+
# substring (#254).
|
|
249
|
+
_s = self.string_format.format(**{k: v or '' for k, v in self.as_dict().items()})
|
|
245
250
|
# remove trailing punctuation from missing nicknames
|
|
246
|
-
_s = _s.replace(
|
|
251
|
+
_s = _s.replace(" ()", "").replace(" ''", "").replace(' ""', "")
|
|
247
252
|
_s = self.C.regexes.space_before_comma.sub(',', _s)
|
|
248
253
|
return self.collapse_whitespace(_s).strip(', ')
|
|
249
254
|
return " ".join(self)
|
|
@@ -928,9 +933,11 @@ class HumanName:
|
|
|
928
933
|
This method happens at the beginning of the :py:func:`parse_full_name`
|
|
929
934
|
before any other processing of the string aside from unicode
|
|
930
935
|
normalization, so it's a good place to do any custom handling in a
|
|
931
|
-
subclass. Runs :py:func:`
|
|
936
|
+
subclass. Runs :py:func:`squash_bidi`, :py:func:`parse_nicknames` and
|
|
937
|
+
:py:func:`squash_emoji`.
|
|
932
938
|
|
|
933
939
|
"""
|
|
940
|
+
self.squash_bidi()
|
|
934
941
|
self.fix_phd()
|
|
935
942
|
self.parse_nicknames()
|
|
936
943
|
self.squash_emoji()
|
|
@@ -1133,6 +1140,16 @@ class HumanName:
|
|
|
1133
1140
|
if re_emoji and re_emoji.search(self._full_name):
|
|
1134
1141
|
self._full_name = re_emoji.sub('', self._full_name)
|
|
1135
1142
|
|
|
1143
|
+
def squash_bidi(self) -> None:
|
|
1144
|
+
"""
|
|
1145
|
+
Remove invisible bidirectional control characters from the input
|
|
1146
|
+
string. They carry no name content but stick to the parts they
|
|
1147
|
+
surround, so parsed attributes stop comparing equal to the clean name.
|
|
1148
|
+
"""
|
|
1149
|
+
re_bidi = self.C.regexes.bidi
|
|
1150
|
+
if re_bidi and re_bidi.search(self._full_name):
|
|
1151
|
+
self._full_name = re_bidi.sub('', self._full_name)
|
|
1152
|
+
|
|
1136
1153
|
def handle_firstnames(self) -> None:
|
|
1137
1154
|
"""
|
|
1138
1155
|
If there are only two parts and one is a title, assume it's a last name
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nameparser
|
|
3
|
-
Version: 1.3.
|
|
3
|
+
Version: 1.3.1
|
|
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
6
|
License: LGPL
|
|
@@ -57,19 +57,35 @@ are optional. Comma-separated format like "Last, First" is also supported.
|
|
|
57
57
|
2. Lastname [Suffix], Title Firstname (Nickname) Middle Middle[,] Suffix [, Suffix]
|
|
58
58
|
3. Title Firstname M Lastname [Suffix], Suffix [Suffix] [, Suffix]
|
|
59
59
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
60
|
+
How It Works
|
|
61
|
+
~~~~~~~~~~~~
|
|
62
|
+
|
|
63
|
+
The parser works in two layers.
|
|
64
|
+
|
|
65
|
+
A **vocabulary layer** recognizes name pieces by what they are, using
|
|
66
|
+
configurable sets of known words: titles ("Dr."), suffixes ("III", "PhD"),
|
|
67
|
+
last-name prefixes ("de la"), conjunctions ("y", "&"), and delimited
|
|
68
|
+
nicknames ("Doc"). Titles and conjunctions chain together to handle complex
|
|
69
|
+
titles like "Asst Secretary of State"; prefixes join forward so "de la Vega"
|
|
70
|
+
stays one last name. This layer doesn't care where in the string a word
|
|
71
|
+
appears — and it's the layer you customize, by adding or removing entries
|
|
72
|
+
in the sets to fit your dataset.
|
|
73
|
+
|
|
74
|
+
A **positional layer** then assigns everything the vocabulary layer didn't
|
|
75
|
+
claim, based purely on where it sits: the first unclaimed word is the first
|
|
76
|
+
name, the last one is the last name, and anything between them is a middle
|
|
77
|
+
name. There is no semantic understanding — "Dr" is a title when it comes
|
|
78
|
+
before a name and a suffix when it comes after ("pre-nominal" and
|
|
79
|
+
"post-nominal" would probably be better names) — and no attempt to correct
|
|
80
|
+
mistakes in the input.
|
|
81
|
+
|
|
82
|
+
It attempts the best guess that can be made with a simple, deterministic,
|
|
83
|
+
rule-based approach — no statistical models or machine learning; the same
|
|
84
|
+
input always parses the same way. The positional layer assumes Western name
|
|
85
|
+
order (given name first), so the main use case is English and other
|
|
86
|
+
languages that share that structure. It can also try to correct the
|
|
87
|
+
capitalization of names that are all upper- or lowercase. It's not perfect,
|
|
88
|
+
but it gets you pretty far.
|
|
73
89
|
|
|
74
90
|
Installation
|
|
75
91
|
------------
|
|
@@ -116,11 +132,8 @@ Quick Start Example
|
|
|
116
132
|
'Juan de la Vega'
|
|
117
133
|
|
|
118
134
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
the difference between 'title' and 'suffix' is positional, not semantic. "Dr" is a title
|
|
122
|
-
when it comes before the name and a suffix when it comes after. ("Pre-nominal"
|
|
123
|
-
and "post-nominal" would probably be better names.)
|
|
135
|
+
Because the positional layer has no semantic understanding, position is
|
|
136
|
+
everything:
|
|
124
137
|
|
|
125
138
|
::
|
|
126
139
|
|
|
@@ -139,10 +152,11 @@ and "post-nominal" would probably be better names.)
|
|
|
139
152
|
Customization
|
|
140
153
|
-------------
|
|
141
154
|
|
|
142
|
-
Your project may need some adjustment for your dataset.
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
155
|
+
Your project may need some adjustment for your dataset. Most customization
|
|
156
|
+
is vocabulary — `customizing the configured pre-defined sets`_ of titles,
|
|
157
|
+
prefixes, etc. that the vocabulary layer matches against. You can also do
|
|
158
|
+
your own pre- or post-processing, or subclass the `HumanName` class for
|
|
159
|
+
deeper changes. See the `full documentation`_ for more information.
|
|
146
160
|
|
|
147
161
|
|
|
148
162
|
`Full documentation`_
|
|
@@ -99,6 +99,21 @@ class HumanNameOutputFormatTests(HumanNameTestBase):
|
|
|
99
99
|
hn.nickname = ''
|
|
100
100
|
self.assertEqual(str(hn), "Rev John A. Kenneth Doe III")
|
|
101
101
|
|
|
102
|
+
def test_name_containing_none_substring_with_none_empty_attribute_default(self) -> None:
|
|
103
|
+
# Regression for #254: with empty_attribute_default = None, __str__
|
|
104
|
+
# scrubbed the literal string 'None' from the formatted output,
|
|
105
|
+
# corrupting real name text containing that substring.
|
|
106
|
+
hn = HumanName("Nonez Smith", None)
|
|
107
|
+
hn.C.empty_attribute_default = None # type: ignore[assignment] # see test_constants.test_empty_attribute_default
|
|
108
|
+
self.assertEqual(str(hn), "Nonez Smith")
|
|
109
|
+
|
|
110
|
+
def test_name_none_as_literal_name_with_none_empty_attribute_default(self) -> None:
|
|
111
|
+
# Companion to the #254 regression: a name piece that is exactly
|
|
112
|
+
# 'None' must survive formatting in None-mode.
|
|
113
|
+
hn = HumanName("None Smith", None)
|
|
114
|
+
hn.C.empty_attribute_default = None # type: ignore[assignment] # see test_constants.test_empty_attribute_default
|
|
115
|
+
self.assertEqual(str(hn), "None Smith")
|
|
116
|
+
|
|
102
117
|
def test_empty_field_drops_surrounding_whitespace(self) -> None:
|
|
103
118
|
# issue #139: adjacent whitespace/punctuation should be dropped when a field is empty
|
|
104
119
|
hn = HumanName("John Smith")
|
|
@@ -136,3 +151,27 @@ class HumanNameOutputFormatTests(HumanNameTestBase):
|
|
|
136
151
|
self.m(hn.last, "Smith😊", hn)
|
|
137
152
|
self.assertEqual(str(hn), "∫≜⩕ Smith😊")
|
|
138
153
|
# test cleanup
|
|
154
|
+
|
|
155
|
+
def test_remove_bidi_control_chars(self) -> None:
|
|
156
|
+
# LRM/RLM and friends ride along with copy-pasted names and stick to
|
|
157
|
+
# the parts they surround. Covers every character in the bidi set.
|
|
158
|
+
for mark in ("\u200e", "\u200f", "\u061c", "\u202a", "\u202b",
|
|
159
|
+
"\u202c", "\u202d", "\u202e", "\u2066", "\u2067",
|
|
160
|
+
"\u2068", "\u2069"):
|
|
161
|
+
hn = HumanName(mark + "John" + mark + " Smith")
|
|
162
|
+
self.m(hn.first, "John", hn)
|
|
163
|
+
self.m(hn.last, "Smith", hn)
|
|
164
|
+
|
|
165
|
+
def test_bidi_stripped_name_compares_equal(self) -> None:
|
|
166
|
+
# The reported symptom: an invisible RLM around an RTL name makes the
|
|
167
|
+
# parsed part fail equality against the clean string (issue #266).
|
|
168
|
+
hn = HumanName("\u200fمحمد بن سلمان\u200f")
|
|
169
|
+
self.assertEqual(hn.first, "محمد")
|
|
170
|
+
|
|
171
|
+
def test_keep_bidi_control_chars(self) -> None:
|
|
172
|
+
from nameparser.config import Constants
|
|
173
|
+
constants = Constants()
|
|
174
|
+
constants.regexes.bidi = False # type: ignore[assignment]
|
|
175
|
+
hn = HumanName("\u200fJohn\u200f Smith", constants)
|
|
176
|
+
self.m(hn.first, "\u200fJohn\u200f", hn)
|
|
177
|
+
self.m(hn.last, "Smith", hn)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|