1"""Spain."""
2
3# standard
4from typing import Dict, Set
5
6# local
7from validators.utils import validator
8
9
10def _nif_nie_validation(value: str, number_by_letter: Dict[str, str], special_cases: Set[str]):
11 """Validate if the doi is a NIF or a NIE."""
12 if value in special_cases or len(value) != 9:
13 return False
14 value = value.upper()
15 table = "TRWAGMYFPDXBNJZSQVHLCKE"
16 # If it is not a DNI, convert the first
17 # letter to the corresponding digit
18 numbers = number_by_letter.get(value[0], value[0]) + value[1:8]
19 # doi[8] is control
20 return numbers.isdigit() and value[8] == table[int(numbers) % 23]
21
22
23@validator
24def es_cif(value: str, /):
25 """Validate a Spanish CIF.
26
27 Each company in Spain prior to 2008 had a distinct CIF and has been
28 discontinued. For more information see [wikipedia.org/cif][1].
29
30 The new replacement is to use NIF for absolutely everything. The issue is
31 that there are "types" of NIFs now: company, person [citizen or resident]
32 all distinguished by the first character of the DOI. For this reason we
33 will continue to call CIFs NIFs, that are used for companies.
34
35 This validator is based on [generadordni.es][2].
36
37 [1]: https://es.wikipedia.org/wiki/C%C3%B3digo_de_identificaci%C3%B3n_fiscal
38 [2]: https://generadordni.es/
39
40 Examples:
41 >>> es_cif('B25162520')
42 # Output: True
43 >>> es_cif('B25162529')
44 # Output: ValidationError(func=es_cif, args=...)
45
46 Args:
47 value:
48 DOI string which is to be validated.
49
50 Returns:
51 (Literal[True]): If `value` is a valid DOI string.
52 (ValidationError): If `value` is an invalid DOI string.
53 """
54 if not value or len(value) != 9:
55 return False
56 value = value.upper()
57 table = "JABCDEFGHI"
58 first_chr = value[0]
59 doi_body = value[1:8]
60 control = value[8]
61 if not doi_body.isdigit():
62 return False
63 res = (
64 10
65 - sum(
66 # Multiply each positionally even doi
67 # digit by 2 and sum it all together
68 sum(map(int, str(int(char) * 2))) if index % 2 == 0 else int(char)
69 for index, char in enumerate(doi_body)
70 )
71 % 10
72 ) % 10
73 if first_chr in "ABEH": # Number type
74 return str(res) == control
75 if first_chr in "PSQW": # Letter type
76 return table[res] == control
77 return control in {str(res), table[res]} if first_chr in "CDFGJNRUV" else False
78
79
80@validator
81def es_nif(value: str, /):
82 """Validate a Spanish NIF.
83
84 Each entity, be it person or company in Spain has a distinct NIF. Since
85 we've designated CIF to be a company NIF, this NIF is only for person.
86 For more information see [wikipedia.org/nif][1]. This validator
87 is based on [generadordni.es][2].
88
89 [1]: https://es.wikipedia.org/wiki/N%C3%BAmero_de_identificaci%C3%B3n_fiscal
90 [2]: https://generadordni.es/
91
92 Examples:
93 >>> es_nif('26643189N')
94 # Output: True
95 >>> es_nif('26643189X')
96 # Output: ValidationError(func=es_nif, args=...)
97
98 Args:
99 value:
100 DOI string which is to be validated.
101
102 Returns:
103 (Literal[True]): If `value` is a valid DOI string.
104 (ValidationError): If `value` is an invalid DOI string.
105 """
106 number_by_letter = {"L": "0", "M": "0", "K": "0"}
107 special_cases = {"X0000000T", "00000000T", "00000001R"}
108 return _nif_nie_validation(value, number_by_letter, special_cases)
109
110
111@validator
112def es_nie(value: str, /):
113 """Validate a Spanish NIE.
114
115 The NIE is a tax identification number in Spain, known in Spanish
116 as the NIE, or more formally the Número de identidad de extranjero.
117 For more information see [wikipedia.org/nie][1]. This validator
118 is based on [generadordni.es][2].
119
120 [1]: https://es.wikipedia.org/wiki/N%C3%BAmero_de_identidad_de_extranjero
121 [2]: https://generadordni.es/
122
123 Examples:
124 >>> es_nie('X0095892M')
125 # Output: True
126 >>> es_nie('X0095892X')
127 # Output: ValidationError(func=es_nie, args=...)
128
129 Args:
130 value:
131 DOI string which is to be validated.
132
133 Returns:
134 (Literal[True]): If `value` is a valid DOI string.
135 (ValidationError): If `value` is an invalid DOI string.
136 """
137 number_by_letter = {"X": "0", "Y": "1", "Z": "2"}
138 # NIE must must start with X Y or Z
139 if value and value[0] in number_by_letter:
140 return _nif_nie_validation(value, number_by_letter, {"X0000000T"})
141 return False
142
143
144@validator
145def es_doi(value: str, /):
146 """Validate a Spanish DOI.
147
148 A DOI in spain is all NIF / CIF / NIE / DNI -- a digital ID.
149 For more information see [wikipedia.org/doi][1]. This validator
150 is based on [generadordni.es][2].
151
152 [1]: https://es.wikipedia.org/wiki/Identificador_de_objeto_digital
153 [2]: https://generadordni.es/
154
155 Examples:
156 >>> es_doi('X0095892M')
157 # Output: True
158 >>> es_doi('X0095892X')
159 # Output: ValidationError(func=es_doi, args=...)
160
161 Args:
162 value:
163 DOI string which is to be validated.
164
165 Returns:
166 (Literal[True]): If `value` is a valid DOI string.
167 (ValidationError): If `value` is an invalid DOI string.
168 """
169 return es_nie(value) or es_nif(value) or es_cif(value)