1use crate::digit_string::DigitString;
3use crate::error::Error;
4
5mod vocabulary;
6
7use super::{LangInterpreter, MorphologicalMarker};
8use vocabulary::INSIGNIFICANT;
9
10fn lemmatize(word: &str) -> &str {
11 if word.ends_with("os") && word != "dos" || word.ends_with("as") {
13 word.trim_end_matches('s')
14 } else if word.ends_with("es") && word != "tres" {
15 word.trim_end_matches("es")
16 } else {
17 word
18 }
19}
20
21#[derive(Default)]
22pub struct Spanish {}
23
24impl Spanish {
25 pub fn new() -> Self {
26 Default::default()
27 }
28}
29
30impl LangInterpreter for Spanish {
31 fn apply(&self, num_func: &str, b: &mut DigitString) -> Result<(), Error> {
32 let num_marker = self.get_morph_marker(num_func);
33 if !b.is_empty() && num_marker != b.marker && !num_marker.is_fraction() {
34 return Err(Error::Overlap);
35 }
36 let status = match lemmatize(num_func) {
37 "cero" => b.put(b"0"),
38 "un" | "uno" | "una" if b.peek(2) != b"10" && b.peek(2) != b"20" => b.put(b"1"),
39 "primer" | "primero" | "primera" => b.put(b"1"),
40 "dos" if b.peek(2) != b"10" && b.peek(2) != b"20" => b.put(b"2"),
41 "segundo" if b.marker.is_ordinal() => b.put(b"2"),
42 "segunda" => b.put(b"2"),
43 "tres" if b.peek(2) != b"10" && b.peek(2) != b"20" => b.put(b"3"),
44 "tercer" | "tercero" | "tercera" => b.put(b"3"),
45 "cuatro" if b.peek(2) != b"10" && b.peek(2) != b"20" => b.put(b"4"),
46 "cuarto" | "cuarta" => b.put(b"4"),
47 "cinco" if b.peek(2) != b"10" && b.peek(2) != b"20" => b.put(b"5"),
48 "quinto" | "quinta" => b.put(b"5"),
49 "seis" if b.peek(2) != b"10" && b.peek(2) != b"20" => b.put(b"6"),
50 "sexto" | "sexta" => b.put(b"6"),
51 "siete" if b.peek(2) != b"10" && b.peek(2) != b"20" => b.put(b"7"),
52 "séptimo" | "séptima" => b.put(b"7"),
53 "ocho" if b.peek(2) != b"10" && b.peek(2) != b"20" => b.put(b"8"),
54 "octavo" | "octava" => b.put(b"8"),
55 "nueve" if b.peek(2) != b"10" && b.peek(2) != b"20" => b.put(b"9"),
56 "noveno" | "novena" => b.put(b"9"),
57 "diez" | "décimo" | "décima" => b.put(b"10"),
58 "once" | "undécimo" | "undécima" | "decimoprimero" | "decimoprimera" | "onceavo" => {
59 b.put(b"11")
60 }
61 "doce" | "duodécimo" | "duodécima" | "decimosegundo" | "decimosegunda" | "doceavo" => {
62 b.put(b"12")
63 }
64 "trece" | "decimotercero" | "decimotercera" | "treceavo" => b.put(b"13"),
65 "catorce" | "decimocuarto" | "decimocuarta" | "catorceavo" => b.put(b"14"),
66 "quince" | "decimoquinto" | "decimoquinta" | "quinceavo" => b.put(b"15"),
67 "dieciseis" | "dieciséis" | "decimosexto" | "decimosexta" | "deciseisavo" => {
68 b.put(b"16")
69 }
70 "diecisiete" | "decimoséptimo" | "decimoséptima" | "diecisieteavo" => b.put(b"17"),
71 "dieciocho" | "decimoctavo" | "decimoctava" | "dieciochoavo" => b.put(b"18"),
72 "diecinueve" | "decimonoveno" | "decimonovena" | "decinueveavo" => b.put(b"19"),
73 "veinte" | "vigésimo" | "vigésima" | "veintavo" | "veinteavo" => b.put(b"20"),
74 "veintiuno" | "veintiuna" | "veintiunoavo" => b.put(b"21"),
75 "veintidós" | "veintidos" | "veintidosavo" => b.put(b"22"),
76 "veintitrés" | "veintitres" | "veintitresavo" => b.put(b"23"),
77 "veinticuatro" | "veinticuatroavo" => b.put(b"24"),
78 "veinticinco" | "veinticincoavo" => b.put(b"25"),
79 "veintiseis" | "veintiséis" | "veintiseisavo" => b.put(b"26"),
80 "veintisiete" | "veintisieteavo" => b.put(b"27"),
81 "veintiocho" | "veintiochoavo" => b.put(b"28"),
82 "veintinueve" | "veintinueveavo" => b.put(b"29"),
83 "treinta" | "trigésimo" | "trigésima" | "treintavo" => b.put(b"30"),
84 "cuarenta" | "cuadragésimo" | "cuadragésima" | "cuarentavo" => b.put(b"40"),
85 "cincuenta" | "quincuagésimo" | "quincuagésima" | "cincuentavo" => b.put(b"50"),
86 "sesenta" | "sexagésimo" | "sexagésima" | "sesentavo" => b.put(b"60"),
87 "setenta" | "septuagésimo" | "septuagésima" | "setentavo" => b.put(b"70"),
88 "ochenta" | "octogésimo" | "octogésima" | "ochentavo" => b.put(b"80"),
89 "noventa" | "nonagésimo" | "nonagésima" | "noventavo" => b.put(b"90"),
90 "cien" | "ciento" | "centésimo" | "centésima" | "centavo" => b.put(b"100"),
91 "dosciento" | "doscienta" | "ducentésimo" | "ducentésima" => b.put(b"200"),
92 "tresciento" | "trescienta" | "tricentésimo" | "tricentésima" => b.put(b"300"),
93 "cuatrociento" | "cuatrocienta" | "quadringentésimo" | "quadringentésima" => {
94 b.put(b"400")
95 }
96 "quiniento" | "quinienta" | "quingentésimo" | "quingentésima" => b.put(b"500"),
97 "seisciento" | "seiscienta" | "sexcentésimo" | "sexcentésima" => b.put(b"600"),
98 "seteciento" | "setecienta" | "septingentésimo" | "septingentésima" => b.put(b"700"),
99 "ochociento" | "ochocienta" | "octingentésimo" | "octingentésima" => b.put(b"800"),
100 "noveciento" | "novecienta" | "noningentésimo" | "noningentésima" => b.put(b"900"),
101 "mil" | "milésimo" | "milésima" if b.is_range_free(3, 5) => {
102 let peek = b.peek(2);
103 if peek == b"1" {
104 Err(Error::Overlap)
105 } else {
106 b.shift(3)
107 }
108 }
109 "millon" | "millón" | "millonésimo" | "millonésima" if b.is_range_free(6, 8) => {
110 b.shift(6)
111 }
112 "y" if b.len() >= 2 => Err(Error::Incomplete),
113
114 _ => Err(Error::NaN),
115 };
116 if status.is_ok() {
117 b.marker = num_marker;
118 if b.marker.is_fraction() {
119 b.freeze()
120 }
121 }
122 status
123 }
124
125 fn apply_decimal(&self, decimal_func: &str, b: &mut DigitString) -> Result<(), Error> {
126 self.apply(decimal_func, b)
127 }
128
129 fn check_decimal_separator(&self, word: &str) -> Option<char> {
130 match word {
131 "coma" => Some(','),
132 "punto" => Some('.'),
133 _ => None,
134 }
135 }
136
137 fn format_and_value(&self, b: &DigitString) -> (String, f64) {
138 let repr = b.to_string();
139 let val: f64 = repr.parse().unwrap();
140 match b.marker {
141 MorphologicalMarker::Fraction(_) => (format!("1/{repr}"), val.recip()),
142 MorphologicalMarker::Ordinal(marker) => (format!("{repr}{marker}"), val),
143 MorphologicalMarker::None => (repr, val),
144 }
145 }
146
147 fn format_decimal_and_value(
148 &self,
149 int: &DigitString,
150 dec: &DigitString,
151 sep: char,
152 ) -> (String, f64) {
153 let sint = int.to_string();
154 let sdec = dec.to_string();
155 let val = format!("{sint}.{sdec}").parse().unwrap();
156 (format!("{sint}{sep}{sdec}"), val)
157 }
158
159 fn get_morph_marker(&self, word: &str) -> MorphologicalMarker {
160 let sing = lemmatize(word).trim_start_matches("decimo");
161 let is_plur = word.ends_with('s');
162 match sing {
163 "primer" => MorphologicalMarker::Ordinal(".ᵉʳ"),
164 "primero" | "segundo" | "tercero" | "cuarto" | "quinto" | "sexto" | "séptimo"
165 | "octavo" | "ctavo" | "noveno" => {
166 MorphologicalMarker::Ordinal(if is_plur { "ᵒˢ" } else { "º" })
167 }
168 "primera" | "segunda" | "tercera" | "cuarta" | "quinta" | "sexta" | "séptima"
169 | "octava" | "ctava" | "novena" => {
170 MorphologicalMarker::Ordinal(if is_plur { "ᵃˢ" } else { "ª" })
171 }
172 ord if ord.ends_with("imo") => {
173 MorphologicalMarker::Ordinal(if is_plur { "ᵒˢ" } else { "º" })
174 }
175 ord if ord.ends_with("ima") => {
176 MorphologicalMarker::Ordinal(if is_plur { "ᵃˢ" } else { "ª" })
177 }
178 ord if ord.ends_with("avo") => MorphologicalMarker::Fraction("avo"),
179 _ => MorphologicalMarker::None,
180 }
181 }
182
183 fn is_linking(&self, word: &str) -> bool {
184 INSIGNIFICANT.contains(word)
185 }
186}
187
188#[cfg(test)]
189mod tests {
190 use super::*;
191 use crate::word_to_digit::{replace_numbers_in_text, text2digits};
192
193 macro_rules! assert_text2digits {
194 ($text:expr, $res:expr) => {
195 let f = Spanish {};
196 let res = text2digits($text, &f);
197 dbg!(&res);
198 assert!(res.is_ok());
199 assert_eq!(res.unwrap(), $res)
200 };
201 }
202
203 macro_rules! assert_replace_numbers {
204 ($text:expr, $res:expr) => {
205 let f = Spanish {};
206 assert_eq!(replace_numbers_in_text($text, &f, 10.0), $res)
207 };
208 }
209
210 macro_rules! assert_replace_all_numbers {
211 ($text:expr, $res:expr) => {
212 let f = Spanish {};
213 assert_eq!(replace_numbers_in_text($text, &f, 0.0), $res)
214 };
215 }
216
217 macro_rules! assert_invalid {
218 ($text:expr) => {
219 let f = Spanish {};
220 let res = text2digits($text, &f);
221 assert!(res.is_err());
222 };
223 }
224
225 #[test]
226 fn test_apply_steps() {
227 let f = Spanish {};
228 let mut b = DigitString::new();
229 assert!(f.apply("treinta", &mut b).is_ok());
230 assert!(f.apply("cuatro", &mut b).is_ok());
231 assert!(f.apply("veinte", &mut b).is_err());
232 }
233
234 #[test]
235 fn test_apply() {
236 assert_text2digits!("cero", "0");
237 assert_text2digits!("uno", "1");
238 assert_text2digits!("nueve", "9");
239 assert_text2digits!("diez", "10");
240 assert_text2digits!("once", "11");
241 assert_text2digits!("quince", "15");
242 assert_text2digits!("diecinueve", "19");
243 assert_text2digits!("veinte", "20");
244 assert_text2digits!("veintiuno", "21");
245 assert_text2digits!("treinta", "30");
246 assert_text2digits!("treinta y uno", "31");
247 assert_text2digits!("treinta y dos", "32");
248 assert_text2digits!("treinta y nueve", "39");
249 assert_text2digits!("noventa y nueve", "99");
250 assert_text2digits!("ochenta y cinco", "85");
251 assert_text2digits!("ochenta y uno", "81");
252 assert_text2digits!("cien", "100");
253 assert_text2digits!("ciento uno", "101");
254 assert_text2digits!("ciento quince", "115");
255 assert_text2digits!("doscientos", "200");
256 assert_text2digits!("doscientos uno", "201");
257 assert_text2digits!("mil", "1000");
258 assert_text2digits!("mil uno", "1001");
259 assert_text2digits!("dos mil", "2000");
260 assert_text2digits!("dos mil noventa y nueve", "2099");
261 assert_text2digits!("setenta y cinco mil", "75000");
262 assert_text2digits!("mil novecientos veinte", "1920");
263
264 assert_text2digits!("nueve mil novecientos noventa y nueve", "9999");
265 assert_text2digits!(
266 "novecientos noventa y nueve mil novecientos noventa y nueve",
267 "999999"
268 );
269 assert_text2digits!(
270 "novecientos noventa y nueve mil novecientos noventa y nueve millones novecientos noventa y nueve mil novecientos noventa y nueve",
271 "999999999999"
272 );
273 assert_text2digits!(
274 "cincuenta y tres mil veinte millones doscientos cuarenta y tres mil setecientos veinticuatro",
275 "53020243724"
276 );
277 assert_text2digits!(
278 "cincuenta y un millones quinientos setenta y ocho mil trescientos dos",
279 "51578302"
280 );
281 }
282
283 #[test]
284 fn test_variants() {
285 assert_text2digits!("un millon", "1000000");
286 assert_text2digits!("un millón", "1000000");
287 assert_text2digits!("décimo primero", "11º");
288 assert_text2digits!("decimoprimero", "11º");
289 assert_text2digits!("undécimo", "11º");
290 assert_text2digits!("décimo segundo", "12º");
291 assert_text2digits!("decimosegundo", "12º");
292 assert_text2digits!("duodécimo", "12º");
293 }
294
295 #[test]
296 fn test_ordinals() {
297 assert_text2digits!("vigésimo cuarto", "24º");
298 assert_text2digits!("vigésimo primero", "21º");
299 assert_text2digits!("centésimo primero", "101º");
300 assert_text2digits!("decimosexta", "16ª");
301 assert_text2digits!("decimosextas", "16ᵃˢ");
302 assert_text2digits!("decimosextos", "16ᵒˢ");
303 }
304
305 #[test]
306 fn test_fractions() {
307 assert_text2digits!("doceavo", "1/12");
308 assert_text2digits!("centavo", "1/100");
309 assert_text2digits!("ciento veintiochoavos", "1/128");
310 }
311
312 #[test]
313 fn test_zeroes() {
314 assert_text2digits!("cero", "0");
315 assert_text2digits!("cero uno", "01");
316 assert_text2digits!("cero ocho", "08");
317 assert_text2digits!("cero cero ciento veinticinco", "00125");
318 assert_invalid!("cinco cero");
319 assert_invalid!("cincuenta cero tres");
320 assert_invalid!("cincuenta y tres cero");
321 assert_invalid!("diez cero");
322 }
323
324 #[test]
325 fn test_invalid() {
326 assert_invalid!("mil mil doscientos");
327 assert_invalid!("sesenta quince");
328 assert_invalid!("sesenta cien");
329 assert_invalid!("quince cientos");
330 assert_invalid!("veinte cuarto");
331 assert_invalid!("vigésimo decimocuarto");
332 assert_invalid!("diez cuarto");
333 assert_invalid!("uno mil");
334 }
335
336 #[test]
337 fn test_replace_numbers_integers() {
338 assert_replace_numbers!(
339 "Veinticinco vacas, doce gallinas y ciento veinticinco kg de patatas.",
340 "25 vacas, 12 gallinas y 125 kg de patatas."
341 );
342 assert_replace_numbers!(
343 "trescientos hombres y quinientas mujeres",
344 "300 hombres y 500 mujeres"
345 );
346 assert_replace_numbers!("Mil doscientos sesenta y seis dolares.", "1266 dolares.");
347 assert_replace_numbers!("un dos tres cuatro veinte quince.", "1 2 3 4 20 15.");
348 assert_replace_numbers!(
349 "un, dos, tres, cuatro, veinte, quince.",
350 "1, 2, 3, 4, 20, 15."
351 );
352 assert_replace_numbers!("Mil, doscientos, sesenta y seis.", "1000, 200, 66.");
353 assert_replace_numbers!("Veintiuno, treinta y uno.", "21, 31.");
354 assert_replace_numbers!("treinta y cuatro = treinta cuatro", "34 = 34");
355 }
356
357 #[test]
358 fn test_replace_numbers_formal() {
359 assert_replace_numbers!(
360 "dos setenta y cinco cuarenta y nueve cero dos",
361 "2 75 49 02"
362 );
363 }
364
365 #[test]
366 fn test_and() {
367 assert_replace_numbers!("cincuenta sesenta treinta y once", "50 60 30 y 11");
368 }
369
370 #[test]
371 fn test_replace_numbers_zero() {
372 assert_replace_numbers!("trece mil cero noventa", "13000 090");
373 assert_replace_numbers!("cero", "cero");
374 assert_replace_numbers!("cero cinco", "05");
375 assert_replace_numbers!("cero uno ochenta y cinco", "01 85");
376 assert_replace_numbers!("cero, cinco", "0, 5");
377 }
378
379 #[test]
380 fn test_replace_numbers_ordinals() {
381 assert_replace_numbers!(
382 "Cuarto quinto segundo tercero vigésimo primero centésimo milésimo ducentésimo trigésimo.",
383 "4º 5º segundo 3º 21º 100230º."
384 );
385 assert_replace_numbers!("centésimo trigésimo segundo", "132º");
386 assert_replace_numbers!("centésimo, trigésimo, segundo", "100º, 30º, segundo");
387 assert_replace_numbers!(
388 "Un segundo por favor! Vigésimo segundo es diferente que veinte segundos.",
389 "Un segundo por favor! 22º es diferente que 20 segundos."
390 );
391 assert_replace_numbers!(
392 "Un segundo por favor! Vigésimos segundos es diferente que veinte segundos.",
393 "Un segundo por favor! 22ᵒˢ es diferente que 20 segundos."
394 );
395 assert_replace_all_numbers!("Él ha quedado tercero", "Él ha quedado 3º");
396 assert_replace_all_numbers!("Ella ha quedado tercera", "Ella ha quedado 3ª");
397 assert_replace_all_numbers!("Ellos han quedado terceros", "Ellos han quedado 3ᵒˢ");
398 assert_replace_all_numbers!("Ellas han quedado terceras", "Ellas han quedado 3ᵃˢ");
399 }
400
401 #[test]
402 fn test_replace_numbers_decimals() {
403 assert_replace_numbers!(
404 "doce coma noventa y nueve, ciento veinte coma cero cinco, uno coma doscientos treinta y seis, uno coma dos tres y seis.",
405 "12,99, 120,05, 1,236, 1,2 3 y 6."
406 );
407 assert_replace_numbers!("cero coma quince", "0,15");
408 assert_replace_numbers!("uno coma uno", "1,1");
409 assert_replace_numbers!("uno punto uno", "1.1");
410 assert_replace_numbers!("uno coma cuatrocientos uno", "1,401");
411 assert_replace_numbers!("cero coma cuatrocientos uno", "0,401");
412 }
413
414 #[test]
415 fn test_isolates() {
416 assert_replace_numbers!(
417 "Un momento por favor! treinta y un gatos. Uno dos tres cuatro!",
418 "Un momento por favor! 31 gatos. 1 2 3 4!"
419 );
420 assert_replace_numbers!("Ni uno. Uno uno. Treinta y uno", "Ni uno. 1 1. 31");
421 }
422
423 #[test]
424 fn test_isolates_with_noise() {
425 assert_replace_numbers!(
426 "Entonces dos con tres con siete y ocho mas cuatro menos cinco son nueve exacto",
427 "Entonces 2 con 3 con 7 y 8 mas 4 menos 5 son 9 exacto"
428 );
429 }
430}