1use bitflags::bitflags;
7
8use crate::digit_string::DigitString;
9use crate::error::Error;
10use crate::tokenizer::WordSplitter;
11
12mod vocabulary;
13
14use super::{LangInterpreter, MorphologicalMarker};
15use vocabulary::INSIGNIFICANT;
16
17fn lemmatize(word: &str) -> &str {
18 if word.ends_with("tes")
20 || word.ends_with("ter")
21 || word.ends_with("ten")
22 || word.ends_with("tem")
23 {
24 word.trim_end_matches(['s', 'n', 'm', 'r'])
25 } else {
26 word
27 }
28}
29
30bitflags! {
31 struct Excludable: u64 {
34 const TENS = 1;
35 }
36}
37
38pub struct German {
39 word_splitter: WordSplitter,
40}
41
42impl Default for German {
43 fn default() -> Self {
44 Self {
45 word_splitter: WordSplitter::new([
46 "billion",
47 "billionste",
48 "milliarden",
49 "milliarde",
50 "milliardste",
51 "millionen",
52 "million",
53 "millionste",
54 "tausend",
55 "tausendste",
56 "hundert",
57 "hundertste",
58 "und",
59 ])
60 .unwrap(),
61 }
62 }
63}
64
65impl German {
66 pub fn new() -> Self {
67 Default::default()
68 }
69}
70
71impl LangInterpreter for German {
72 fn apply(&self, num_func: &str, b: &mut DigitString) -> Result<(), Error> {
73 let lemma = lemmatize(num_func);
75 if self.word_splitter.is_splittable(lemma) {
76 return match self.exec_group(self.word_splitter.split(lemma)) {
77 Ok(ds) => {
78 if ds.len() > 3 && ds.len() <= 6 && !b.is_range_free(3, 5) {
79 return Err(Error::Overlap);
80 }
81 b.put(&ds)?;
82 if ds.marker.is_ordinal() {
83 b.marker = ds.marker;
84 b.freeze()
85 }
86 Ok(())
87 }
88 Err(err) => Err(err),
89 };
90 }
91 let blocked = Excludable::from_bits_truncate(b.flags);
92 let mut to_block = Excludable::empty();
93
94 let status = match lemma {
95 "null" => b.put(b"0"),
96 "ein" | "eins" | "erste" if b.is_free(2) => {
97 to_block = Excludable::TENS;
98 b.put(b"1")
99 }
100 "zwei" | "zwo" | "zweite" if b.is_free(2) => {
101 to_block = Excludable::TENS;
102 b.put(b"2")
103 }
104 "drei" | "dritte" if b.is_free(2) => {
105 to_block = Excludable::TENS;
106 b.put(b"3")
107 }
108 "vier" | "vierte" if b.is_free(2) => {
109 to_block = Excludable::TENS;
110 b.put(b"4")
111 }
112 "fünf" | "fünfte" if b.is_free(2) => {
113 to_block = Excludable::TENS;
114 b.put(b"5")
115 }
116 "sechs" | "sechste" if b.is_free(2) => {
117 to_block = Excludable::TENS;
118 b.put(b"6")
119 }
120 "sieben" | "siebte" if b.is_free(2) => {
121 to_block = Excludable::TENS;
122 b.put(b"7")
123 }
124 "acht" | "achte" if b.is_free(2) => {
125 to_block = Excludable::TENS;
126 b.put(b"8")
127 }
128 "neun" | "neunte" if b.is_free(2) => {
129 to_block = Excludable::TENS;
130 b.put(b"9")
131 }
132 "zehn" | "zehnte" => b.put(b"10"),
133 "elf" | "elfte" => b.put(b"11"),
134 "zwölf" | "zwölfte" => b.put(b"12"),
135 "dreizehn" | "dreizehnte" => b.put(b"13"),
136 "vierzehn" | "vierzehnte" => b.put(b"14"),
137 "fünfzehn" | "fünfzehnte" => b.put(b"15"),
138 "sechzehn" | "sechzehnte" => b.put(b"16"),
139 "siebzehn" | "siebzehnte" => b.put(b"17"),
140 "achtzehn" | "achtzehnte" => b.put(b"18"),
141 "neunzehn" | "neunzehnte" => b.put(b"19"),
142 "zwanzig" | "zwanzigste" if !blocked.contains(Excludable::TENS) => {
143 b.put_digit_at(b'2', 1)
144 }
145 "dreißig" | "dreissig" | "dreißigste" | "dreissigste"
146 if !blocked.contains(Excludable::TENS) =>
147 {
148 b.put_digit_at(b'3', 1)
149 }
150 "vierzig" | "vierzigste" if !blocked.contains(Excludable::TENS) => {
151 b.put_digit_at(b'4', 1)
152 }
153 "fünfzig" | "fünfzigste" if !blocked.contains(Excludable::TENS) => {
154 b.put_digit_at(b'5', 1)
155 }
156 "sechzig" | "sechzigste" if !blocked.contains(Excludable::TENS) => {
157 b.put_digit_at(b'6', 1)
158 }
159 "siebzig" | "siebzigste" if !blocked.contains(Excludable::TENS) => {
160 b.put_digit_at(b'7', 1)
161 }
162 "achtzig" | "achtzigste" if !blocked.contains(Excludable::TENS) => {
163 b.put_digit_at(b'8', 1)
164 }
165 "neunzig" | "neunzigste" if !blocked.contains(Excludable::TENS) => {
166 b.put_digit_at(b'9', 1)
167 }
168 "hundert" | "hundertste" => {
169 let peek = b.peek(2);
170 if peek.len() == 1 || peek < b"20" {
171 b.shift(2)
172 } else {
173 Err(Error::Overlap)
174 }
175 }
176 "tausend" | "tausendste" if b.is_range_free(3, 5) => b.shift(3),
177 "million" | "millionen" | "millionste" if b.is_range_free(6, 8) => b.shift(6),
178 "milliarde" | "milliarden" | "milliardste" => b.shift(9),
179 "billion" | "billionste" => b.shift(12),
180 "und" => Err(Error::Incomplete),
181
182 _ => Err(Error::NaN),
183 };
184 if status.is_ok() {
185 b.flags = to_block.bits();
186 if lemma.ends_with("te") {
187 b.marker = self.get_morph_marker(lemma);
188 b.freeze();
189 }
190 if lemma == "eins" {
191 b.freeze();
192 }
193 } else {
194 b.flags = 0;
195 }
196 status
197 }
198
199 fn apply_decimal(&self, decimal_func: &str, b: &mut DigitString) -> Result<(), Error> {
200 match decimal_func {
201 "null" => b.push(b"0"),
202 "eins" => b.push(b"1"),
203 "zwei" => b.push(b"2"),
204 "drei" => b.push(b"3"),
205 "vier" => b.push(b"4"),
206 "fünf" => b.push(b"5"),
207 "sechs" => b.push(b"6"),
208 "sieben" => b.push(b"7"),
209 "acht" => b.push(b"8"),
210 "neun" => b.push(b"9"),
211 _ => Err(Error::NaN),
212 }
213 }
214
215 fn check_decimal_separator(&self, word: &str) -> Option<char> {
216 match word {
217 "komma" => Some(','),
218 "punkt" => Some('.'),
219 _ => None,
220 }
221 }
222
223 fn format_and_value(&self, b: &DigitString) -> (String, f64) {
224 let repr = b.to_string();
225 let val: f64 = repr.parse().unwrap();
226 if let MorphologicalMarker::Ordinal(marker) = b.marker {
227 (format!("{}{}", b.to_string(), marker), val)
228 } else {
229 (repr, val)
230 }
231 }
232
233 fn format_decimal_and_value(
234 &self,
235 int: &DigitString,
236 dec: &DigitString,
237 sep: char,
238 ) -> (String, f64) {
239 let irepr = int.to_string();
240 let drepr = dec.to_string();
241 let frepr = format!("{irepr}{sep}{drepr}");
242 let val = format!("{irepr}.{drepr}").parse().unwrap();
243 (frepr, val)
244 }
245
246 fn get_morph_marker(&self, word: &str) -> MorphologicalMarker {
247 if word.ends_with("te") {
248 MorphologicalMarker::Ordinal(".")
249 } else {
250 MorphologicalMarker::None
251 }
252 }
253
254 fn is_linking(&self, word: &str) -> bool {
255 INSIGNIFICANT.contains(word)
256 }
257}
258
259#[cfg(test)]
260mod tests {
261 use super::German;
262 use crate::word_to_digit::{replace_numbers_in_text, text2digits};
263
264 macro_rules! assert_text2digits {
265 ($text:expr, $res:expr) => {
266 let f = German::new();
267 let res = text2digits($text, &f);
268 dbg!(&res);
269 assert!(res.is_ok());
270 assert_eq!(res.unwrap(), $res)
271 };
272 }
273
274 macro_rules! assert_replace_numbers {
275 ($text:expr, $res:expr) => {
276 let f = German::new();
277 assert_eq!(replace_numbers_in_text($text, &f, 10.0), $res)
278 };
279 }
280
281 macro_rules! assert_replace_all_numbers {
282 ($text:expr, $res:expr) => {
283 let f = German::new();
284 assert_eq!(replace_numbers_in_text($text, &f, 0.0), $res)
285 };
286 }
287
288 macro_rules! assert_invalid {
289 ($text:expr) => {
290 let f = German::new();
291 let res = text2digits($text, &f);
292 assert!(res.is_err());
293 };
294 }
295
296 #[test]
297 fn test_apply() {
298 assert_text2digits!("fünfundachtzig", "85");
299 assert_text2digits!("einundachtzig", "81");
300 assert_text2digits!("fünfzehn", "15");
301 assert_text2digits!("zwei und vierzig", "42");
302 assert_text2digits!("einhundertfünfzehn", "115");
303 assert_text2digits!("einhundert fünfzehn", "115");
304 assert_text2digits!("ein hundert fünfzehn", "115");
305 assert_text2digits!("fünfundsiebzigtausend", "75000");
306 assert_text2digits!("vierzehntausend", "14000");
307 assert_text2digits!("eintausendneunhundertzwanzig", "1920");
308 assert_text2digits!("neunzehnhundertdreiundsiebzig", "1973");
309 assert_text2digits!(
310 "dreiundfünfzig Milliarden zweihundertdreiundvierzigtausendsiebenhundertvierundzwanzig",
311 "53000243724"
312 );
313 assert_text2digits!(
314 "einundfünfzig Millionen fünfhundertachtundsiebzigtausenddreihundertzwei",
315 "51578302"
316 );
317 }
318
319 #[test]
320 fn test_ordinals() {
321 assert_text2digits!("einundzwanzigster", "21.");
322 assert_text2digits!("eintausendzweihundertdreißigster", "1230.");
323 assert_text2digits!("fünfzigster", "50.");
324 assert_text2digits!("neunundvierzigster", "49.");
325 }
326
327 #[test]
328 fn test_zeroes() {
329 assert_text2digits!("null", "0");
330 assert_text2digits!("null acht", "08");
331 assert_text2digits!("null null hundertfünfundzwanzig", "00125");
332 assert_invalid!("fünf null");
333 assert_invalid!("fünfzignullzwei");
334 assert_invalid!("fünfzigdreinull");
335 }
336
337 #[test]
338 fn test_invalid() {
339 assert_invalid!("tausendtausendzweihundert");
340 assert_invalid!("sechzigfünfzehn");
341 assert_invalid!("sechzighundert");
342 assert_invalid!("zwei und vierzig und");
343 assert_invalid!("dreißig und elf");
344 assert_invalid!("ein und zehn");
345 assert_invalid!("wei und neunzehn");
346 assert_invalid!("zwanzig zweitausend");
347 assert_invalid!("eine und zwanzig");
348 assert_invalid!("eins und zwanzig");
349 assert_invalid!("neun zwanzig");
350 }
351
352 #[test]
353 fn test_replace_intergers() {
354 assert_replace_numbers!(
355 "fünfundzwanzig Kühe, zwölf Hühner und einhundertfünfundzwanzig kg Kartoffeln.",
356 "25 Kühe, 12 Hühner und 125 kg Kartoffeln."
357 );
358 assert_replace_numbers!(
359 "Eintausendzweihundertsechsundsechzig Dollar.",
360 "1266 Dollar."
361 );
362 assert_replace_numbers!("einundzwanzig, einunddreißig.", "21, 31.");
363 assert_replace_numbers!("zweiundzwanzig zweitausendeinundzwanzig", "22 2021");
364 assert_replace_numbers!("zwei und zwanzig zwei tausend ein und zwanzig", "22 2021");
365 assert_replace_numbers!(
366 "tausend hundertzweitausend zweihunderttausend vierzehntausend",
367 "1000 102000 200000 14000"
368 );
369 assert_replace_numbers!("eins zwei drei vier zwanzig fünfzehn", "1 2 3 4 20 15");
370 assert_replace_numbers!("eins zwei drei vier fünf und zwanzig.", "1 2 3 4 25.");
371 assert_replace_numbers!("eins zwei drei vier fünfundzwanzig.", "1 2 3 4 25.");
372 assert_replace_numbers!("eins zwei drei vier fünf zwanzig.", "1 2 3 4 5 20.");
373 assert_replace_numbers!(
374 "achtundachtzig sieben hundert, acht und achtzig siebenhundert, achtundachtzig sieben hundert, acht und achtzig sieben hundert",
375 "88 700, 88 700, 88 700, 88 700"
376 );
377 assert_replace_numbers!(
378 "Zahlen wie vierzig fünfhundert Tausend zweiundzwanzig hundert sind gut.",
379 "Zahlen wie 40 500022 100 sind gut."
380 );
381 }
382
383 #[test]
384 fn test_replace_relaxed() {
385 assert_replace_numbers!("vier und dreißig = vierunddreißig", "34 = 34");
386 assert_replace_numbers!("Ein hundert ein und dreißig", "131");
387 assert_replace_numbers!("Einhundert und drei", "103");
388 assert_replace_numbers!(
389 "eins und zwanzig ist nicht einundzwanzig",
390 "1 und 20 ist nicht 21"
391 );
392 assert_replace_numbers!("Einhundert und Ende", "100 und Ende");
393 assert_replace_numbers!("Einhundert und und", "100 und und");
394 assert_replace_numbers!("neun zwanzig", "9 20");
395 }
396
397 #[test]
398 fn test_replace_formal() {
399 assert_replace_numbers!(
400 "plus dreiunddreißig neun sechzig null sechs zwölf einundzwanzig",
401 "plus 33 9 60 06 12 21"
402 );
403
404 assert_replace_numbers!("null null fünf", "005");
405 assert_replace_numbers!("fünf null null", "5 00");
406 assert_replace_numbers!("null", "null");
407 assert_replace_all_numbers!("null", "0");
408 assert_replace_numbers!(
409 "null neun sechzig null sechs zwölf einundzwanzig",
410 "09 60 06 12 21"
411 );
412 assert_replace_numbers!("fünfzig sechzig dreißig und elf", "50 60 30 und 11");
413 assert_replace_numbers!("dreizehntausend null neunzig", "13000 090");
414 }
415
416 #[test]
417 fn test_replace_ordinals() {
418 assert_replace_numbers!(
419 "erster, zweiter, dritter, vierter, fünfter, sechster, siebter, achter, neunter.",
420 "1., 2., 3., 4., 5., 6., 7., 8., 9.."
421 );
422 assert_replace_numbers!(
423 "zehnter, zwanzigster, einundzwanzigster, fünfundzwanzigster, achtunddreißigster, neunundvierzigster, hundertster, eintausendzweihundertdreißigster.",
424 "10., 20., 21., 25., 38., 49., 100., 1230.."
425 );
426 assert_replace_numbers!("zwanzig erste Versuche", "20 erste Versuche");
427 assert_replace_numbers!("zwei tausend zweite", "2002.");
428 assert_replace_numbers!("zweitausendzweite", "2002.");
429 assert_replace_numbers!(
430 "Dies ist eine Liste oder die Einkaufsliste.",
431 "Dies ist eine Liste oder die Einkaufsliste."
432 );
433 assert_replace_numbers!(
434 "In zehnten Jahrzehnten. Und einmal mit den Vereinten.",
435 "In 10. Jahrzehnten. Und einmal mit den Vereinten."
436 );
437 assert_replace_numbers!(
438 "der zweiundzwanzigste erste zweitausendzweiundzwanzig",
439 "der 22. 1. 2022"
440 );
441 assert_replace_numbers!(
442 "der zwei und zwanzigste erste zwei tausend zwei und zwanzig",
443 "der 22. 1. 2022"
444 );
445 assert_replace_all_numbers!(
446 "das erste lustigste hundertste dreißigste beste",
447 "das 1. lustigste 100. 30. beste"
448 );
449 assert_replace_all_numbers!("der dritte und dreißig", "der 3. und 30");
450 assert_replace_all_numbers!(
451 "Es ist ein Buch mit dreitausend Seiten aber nicht das erste.",
452 "Es ist 1 Buch mit 3000 Seiten aber nicht das 1.."
453 );
454 assert_replace_numbers!(
455 "Es ist ein Buch mit dreitausend Seiten aber nicht das erste.",
456 "Es ist ein Buch mit 3000 Seiten aber nicht das erste."
457 );
458 }
459
460 #[test]
461 fn test_replace_decimals() {
462 assert_replace_numbers!(
463 "Die Testreihe ist zwölf komma neunundneunzig, zwölf komma neun, einhundertzwanzig komma null fünf, eins komma zwei drei sechs.",
464 "Die Testreihe ist 12 komma 99, 12,9, 120,05, 1,236."
465 );
466 assert_replace_numbers!(
467 "null komma fünfzehn geht nicht, aber null komma eins fünf",
468 "0 komma 15 geht nicht, aber 0,15"
469 );
470 assert_replace_numbers!(
471 "Pi ist drei Komma eins vier und so weiter",
472 "Pi ist 3,14 und so weiter"
473 );
474 assert_replace_numbers!("drei Punkt eins vier", "3.14");
475 assert_replace_numbers!("komma eins vier", "komma 1 4");
476 assert_replace_all_numbers!("drei komma", "3 komma");
477 assert_replace_numbers!("drei komma", "drei komma");
478 assert_replace_all_numbers!("eins komma erste", "1 komma 1.");
479 }
480
481 #[test]
482 fn test_replace_signed() {
483 assert_replace_numbers!(
484 "Es ist drinnen plus zwanzig Grad und draußen minus fünfzehn Grad.",
485 "Es ist drinnen plus 20 Grad und draußen minus 15 Grad."
486 );
487 }
488
489 #[test]
490 fn test_uppercase() {
491 assert_replace_numbers!("FÜNFZEHN EINS ZEHN EINS", "15 1 10 1");
492 }
493
494 #[test]
495 fn test_isolates() {
496 assert_replace_all_numbers!(
497 "Ich nehme eins. Eins passt nicht!",
498 "Ich nehme 1. 1 passt nicht!"
499 );
500 assert_replace_numbers!(
501 "Ich nehme eins. Eins passt nicht!",
502 "Ich nehme eins. Eins passt nicht!"
503 );
504 assert_replace_all_numbers!("Velma hat eine Spur", "Velma hat eine Spur");
505
506 assert_replace_all_numbers!("Er sieht eine Zwei", "Er sieht eine 2");
507 assert_replace_numbers!("Er sieht eine Zwei", "Er sieht eine Zwei");
508 assert_replace_numbers!("Ich suche ein Buch", "Ich suche ein Buch");
509 assert_replace_numbers!("Er sieht es nicht ein", "Er sieht es nicht ein");
510 assert_replace_all_numbers!("Eine Eins und eine Zwei", "Eine 1 und eine 2");
511 }
514
515 }