rm-date-parser 0.1.0__py3-none-any.whl

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.
@@ -0,0 +1,594 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+ from typing import Iterable, List, Optional
5
+
6
+ try:
7
+ from . import dictionary as Dictionary
8
+ from . import helper as Helper
9
+ from .models import DateElement, LocalDateModel
10
+ except ImportError: # pragma: no cover - fallback for direct module execution
11
+ from dateparserpython import dictionary as Dictionary
12
+ from dateparserpython import helper as Helper
13
+ from dateparserpython.models import DateElement, LocalDateModel
14
+
15
+
16
+ class Parser:
17
+ """
18
+ Python port of the Java Parser class. The public API mirrors the Java
19
+ version: call parse(text) to receive a list of LocalDateModel instances.
20
+ """
21
+
22
+ def __init__(self) -> None:
23
+ self.delim = "-.\\/|:, "
24
+ self.learnedPatternString: Optional[str] = None
25
+ self.learnPattern = False
26
+ self._tokenizer_cache: dict[str, re.Pattern[str]] = {}
27
+
28
+ def parse(self, text: str) -> List[LocalDateModel]:
29
+ date_groups: List[LocalDateModel] = []
30
+ groups = self.getDateGroups(text)
31
+ if not groups:
32
+ return date_groups
33
+ for element in groups:
34
+ localdate = self.getDateFromPhrase(element)
35
+ if localdate is None:
36
+ continue
37
+ localdate.start = element.startPos
38
+ localdate.end = element.endPos
39
+ if element.timeFragment:
40
+ localdate = self.putTimeInDate(localdate, element)
41
+ date_fragment = element.getDateFragment() or ""
42
+ delims = re.sub(r"[A-Za-z0-9]", "", date_fragment)
43
+ found_format = localdate.identified_date_format or ""
44
+ if len(delims) == 2:
45
+ found_format = found_format.replace("$", delims[0])
46
+ found_format = found_format.replace("&", delims[1])
47
+ if len(delims) == 3:
48
+ found_format = found_format.replace("$", delims[0])
49
+ found_format = found_format.replace("&", delims[1] + delims[2])
50
+ if element.isAlphaNumeric and Helper.isFullMonth(element.data):
51
+ found_format = found_format.replace("MMM", "MMMMM")
52
+ if element.hasAmPm:
53
+ found_format = f"{found_format} a"
54
+ localdate.identified_date_format = found_format
55
+ date_groups.append(localdate)
56
+ return date_groups
57
+
58
+ def _tokenize(self, text: str, delimiters: str) -> List[str]:
59
+ if delimiters not in self._tokenizer_cache:
60
+ pattern = "[" + re.escape(delimiters) + "]+"
61
+ self._tokenizer_cache[delimiters] = re.compile(pattern)
62
+ return [token for token in self._tokenizer_cache[delimiters].split(text) if token]
63
+
64
+ def getDateFromPhrase(self, element: DateElement) -> Optional[LocalDateModel]:
65
+ if element.timeFragment is None:
66
+ s = element.data
67
+ else:
68
+ s = element.dateFragment or element.data
69
+ if element.isAlphaNumeric:
70
+ tokens = self._tokenize(s, self.delim)
71
+ if len(tokens) < 3:
72
+ return None
73
+ t1, t2, t3 = tokens[:3]
74
+ if Helper.isDigit(t1):
75
+ year = int(t1)
76
+ if year > 31:
77
+ present_format = "yy$" if year < 99 else "yyyy$"
78
+ if Helper.isDigit(t2):
79
+ present_format = present_format + "dd&MMM"
80
+ day = int(t2)
81
+ month = self.monthToDigit(t3)
82
+ else:
83
+ present_format = present_format + "MMM&dd"
84
+ month = self.monthToDigit(t2)
85
+ day = int(t3)
86
+ local_date = self.getYyyyMmDdProbable(year, month, day)
87
+ if local_date:
88
+ local_date.original_text = element.data
89
+ local_date.identified_date_format = present_format
90
+ return local_date
91
+ if Helper.isDigit(t3):
92
+ year = int(t3)
93
+ if year > 31:
94
+ present_format = "yy" if year < 99 else "yyyy"
95
+ if Helper.isDigit(t1):
96
+ present_format = f"dd$MMM&{present_format}"
97
+ day = int(t1)
98
+ month = self.monthToDigit(t2)
99
+ else:
100
+ present_format = f"MMM$dd&{present_format}"
101
+ month = self.monthToDigit(t1)
102
+ day = int(t2)
103
+ local_date = self.getYyyyMmDdProbable(year, month, day)
104
+ if local_date:
105
+ local_date.original_text = element.data
106
+ local_date.identified_date_format = present_format
107
+ return local_date
108
+ return None
109
+ else:
110
+ if "T" in s or "_" in s:
111
+ return None
112
+ tokens = self._tokenize(s, self.delim)
113
+ if len(tokens) < 3:
114
+ return None
115
+ d1, d2, d3 = (int(token) for token in tokens[:3])
116
+ if d1 > 999:
117
+ local_date = self.getYyyyMmDdProbable(d1, d2, d3)
118
+ if local_date:
119
+ local_date.original_text = element.data
120
+ if self.learnPattern and self.learnedPatternString is not None:
121
+ self.learnedPatternString = local_date.con_date_format
122
+ return local_date
123
+ if 31 < d1 < 100:
124
+ local_date = self.getYyMmDdProbable(d1, d2, d3)
125
+ if local_date:
126
+ local_date.original_text = element.data
127
+ if self.learnPattern and self.learnedPatternString is not None:
128
+ self.learnedPatternString = local_date.con_date_format
129
+ return local_date
130
+ if d3 > 999 and ((0 < d1 < 32) or (0 < d2 < 32)) and d1 > 0 and d2 > 0:
131
+ local_date = self.getDetemintaionForYyyyPrefix(d1, d2, d3)
132
+ if local_date:
133
+ local_date.original_text = element.data
134
+ if self.learnPattern and self.learnedPatternString is not None:
135
+ self.learnedPatternString = local_date.con_date_format
136
+ return local_date
137
+ if 31 < d3 < 100 and ((0 < d1 < 32) or (0 < d2 < 32)):
138
+ local_date = self.getDetemintaionForYyyyPrefix(d1, d2, d3)
139
+ if local_date:
140
+ local_date.original_text = element.data
141
+ if self.learnPattern and self.learnedPatternString is not None:
142
+ self.learnedPatternString = local_date.con_date_format
143
+ return local_date
144
+ return None
145
+
146
+ def putTimeInDate(self, localdate: LocalDateModel, element: DateElement) -> LocalDateModel:
147
+ format_string = "HH:mm:ss"
148
+ s = element.timeFragment or ""
149
+ probable_time_format = format_string
150
+ tokens: Iterable[str]
151
+ if "." in s or "," in s:
152
+ tokens = self._tokenize(s, ":., ")
153
+ hour, minute, second, millis = (int(token) for token in tokens)
154
+ if "." in s:
155
+ probable_time_format = format_string + ".SSS"
156
+ if "," in s:
157
+ probable_time_format = format_string + ",SSS"
158
+ else:
159
+ tokens = self._tokenize(s, ": ")
160
+ hour, minute, second = (int(token) for token in tokens)
161
+ millis = 0
162
+ if element.hasAmPm:
163
+ if "pm" in s.lower():
164
+ hour = hour + 12
165
+ probable_time_format = f"{probable_time_format} aaa"
166
+ format_string = format_string.replace("HH", "hh")
167
+ probable_time_format = probable_time_format.replace("HH", "hh")
168
+ if hour < 24 and minute < 60 and second < 60 and millis < 10000:
169
+ time_piece = f"{hour:02d}:{minute:02d}:{second:02d}"
170
+ if millis >= 0 and ("." in s or "," in s):
171
+ time_piece = f"{time_piece}.{millis:03d}"
172
+ localdate.con_date_format = f"{localdate.con_date_format} {format_string}"
173
+ localdate.date_time_string = f"{localdate.date_time_string} {time_piece}"
174
+ sep = "'T'" if element.dateTimeSeprator == "T" else element.dateTimeSeprator
175
+ identified = localdate.identified_date_format or ""
176
+ localdate.identified_date_format = f"{identified}{sep}{probable_time_format}"
177
+ return localdate
178
+
179
+ def getDetemintaionForYyPrefix(self, d1: int, d2: int, pYear: int) -> Optional[LocalDateModel]:
180
+ return self.getDetemintaionForYyyyPrefix(d1, d2, pYear)
181
+
182
+ def getDetemintaionForYyyyPrefix(self, d1: int, d2: int, pYear: int) -> Optional[LocalDateModel]:
183
+ if d1 > 12 and 0 < d2 <= 12:
184
+ p_date = d1
185
+ p_month = d2
186
+ local_date = self.getYyyyMmDdProbable(pYear, p_month, p_date)
187
+ if local_date:
188
+ if 9 < pYear < 100:
189
+ local_date.identified_date_format = "dd$MM&yy"
190
+ if 99 < pYear < 10000:
191
+ local_date.identified_date_format = "dd$MM&yyyy"
192
+ return local_date
193
+ if d2 > 12 and 0 < d1 <= 12:
194
+ p_date = d2
195
+ p_month = d1
196
+ local_date = self.getYyyyMmDdProbable(pYear, p_month, p_date)
197
+ if local_date:
198
+ if 9 < pYear < 100:
199
+ local_date.identified_date_format = "MM$dd&yy"
200
+ if 99 < pYear < 10000:
201
+ local_date.identified_date_format = "MM$dd&yyyy"
202
+ return local_date
203
+ if d1 <= 12 and d2 <= 12:
204
+ p_month = d1
205
+ p_date = d2
206
+ local_date = self.getYyyyMmDdProbable(pYear, p_month, p_date)
207
+ if local_date:
208
+ if 9 < pYear < 100:
209
+ local_date.identified_date_format = "MM$dd&yy"
210
+ if 99 < pYear < 10000:
211
+ local_date.identified_date_format = "MM$dd&yyyy"
212
+ return local_date
213
+ return None
214
+
215
+ def getYyMmDdProbable(self, pYear: int, pMonth: int, pDay: int) -> Optional[LocalDateModel]:
216
+ return self.getYyyyMmDdProbable(pYear, pMonth, pDay)
217
+
218
+ def getYyyyMmDdProbable(self, pYear: int, pMonth: int, pDay: int) -> Optional[LocalDateModel]:
219
+ year = pYear
220
+ month = -100
221
+ day = -100
222
+ format_probable = ""
223
+ if 0 < pMonth < 13:
224
+ month = pMonth
225
+ format_probable = "MM&dd"
226
+ elif pMonth > 12 and pDay < 13:
227
+ month = pDay
228
+ pDay = pMonth
229
+ format_probable = "dd&MM"
230
+ else:
231
+ return None
232
+ if month == 2:
233
+ if year % 4 > 0 and pDay < 29:
234
+ day = pDay
235
+ elif year % 4 == 0 and pDay < 30:
236
+ day = pDay
237
+ else:
238
+ return None
239
+ elif self.is31DayMonth(month):
240
+ if pDay < 32:
241
+ day = pDay
242
+ else:
243
+ return None
244
+ elif self.is30DayMonth(month):
245
+ if pDay < 31:
246
+ day = pDay
247
+ else:
248
+ return None
249
+ else:
250
+ return None
251
+ localdate = LocalDateModel()
252
+ localdate.date_time_string = f"{year:04d}-{month:02d}-{day:02d}"
253
+ localdate.con_date_format = "yyyy-MM-dd"
254
+ if 9 < year < 100:
255
+ format_probable = "yy$" + format_probable
256
+ if 999 < year < 10000:
257
+ format_probable = "yyyy$" + format_probable
258
+ localdate.identified_date_format = format_probable
259
+ return localdate
260
+
261
+ def getDateGroups(self, text: str) -> Optional[List[DateElement]]:
262
+ date_groups: Optional[List[DateElement]] = None
263
+ possible_date = ["\x00"] * 30
264
+ possible_time = ["\x00"] * 17
265
+ i = 0
266
+ end_found_earlier = False
267
+ month_determined = False
268
+ search_for_time_piece = False
269
+ time_frg_length = 0
270
+ marker = "0"
271
+ is_alphanumeric = False
272
+ date_time_separator = " "
273
+ whitespace_count = 0
274
+ tree = Dictionary.patternPredictionTree
275
+ month = Dictionary.monthPredictionTree
276
+ time = Dictionary.timePredictionTree
277
+ text_length = len(text)
278
+ for count in range(text_length):
279
+ c = text[count]
280
+ if i == 0:
281
+ tree = Dictionary.patternPredictionTree
282
+ is_alphanumeric = False
283
+ if i > 1 and possible_date[i - 1] == " " and c == " ":
284
+ whitespace_count += 1
285
+ continue
286
+ if search_for_time_piece:
287
+ time_determined = time.explict_date_fragment
288
+ if time_frg_length > 12 and possible_time[time_frg_length - 1] == " ":
289
+ time_determined = True
290
+ if Helper.isDigit(c):
291
+ time = time.get_child("D")
292
+ else:
293
+ time = time.get_child(c.lower())
294
+ if time is None:
295
+ if time_determined:
296
+ date_groups = self.addTimeFragment(
297
+ date_groups, possible_date, possible_time, count, i, time_frg_length, date_time_separator
298
+ )
299
+ if time_frg_length > 0 and self.isValidTimeFragmentWithEndingDelim(possible_time):
300
+ possible_time[time_frg_length - 1] = " "
301
+ if i > 0:
302
+ possible_date[i - 1] = " "
303
+ date_groups = self.addTimeFragment(
304
+ date_groups, possible_date, possible_time, count, i - 1, time_frg_length, date_time_separator
305
+ )
306
+ search_for_time_piece = False
307
+ possible_date = self.nullifyBuffer(possible_date)
308
+ possible_time = self.nullifyBuffer(possible_time)
309
+ end_found_earlier = False
310
+ time = Dictionary.timePredictionTree
311
+ i = 0
312
+ whitespace_count = 0
313
+ time_frg_length = 0
314
+ continue
315
+ else:
316
+ if time_determined:
317
+ if count == text_length - 1:
318
+ date_groups = self.addTimeFragment(
319
+ date_groups, possible_date, possible_time, count, i, time_frg_length, date_time_separator
320
+ )
321
+ search_for_time_piece = False
322
+ possible_date = self.nullifyBuffer(possible_date)
323
+ possible_time = self.nullifyBuffer(possible_time)
324
+ end_found_earlier = False
325
+ time = Dictionary.timePredictionTree
326
+ i = 0
327
+ whitespace_count = 0
328
+ time_frg_length = 0
329
+ continue
330
+ if c == " ":
331
+ try:
332
+ next_chars = text[count + 1 : count + 3]
333
+ if next_chars.lower() in {"am", "pm"}:
334
+ possible_time[time_frg_length] = c
335
+ time_frg_length += 1
336
+ possible_date[i] = c
337
+ i += 1
338
+
339
+ possible_time[time_frg_length] = text[count + 1]
340
+ time_frg_length += 1
341
+ possible_date[i] = text[count + 1]
342
+ i += 1
343
+
344
+ possible_time[time_frg_length] = text[count + 2]
345
+ time_frg_length += 1
346
+ possible_date[i] = text[count + 2]
347
+ i += 1
348
+ except IndexError:
349
+ pass
350
+ date_groups = self.addTimeFragment(
351
+ date_groups, possible_date, possible_time, count, i, time_frg_length, date_time_separator
352
+ )
353
+ search_for_time_piece = False
354
+ possible_date = self.nullifyBuffer(possible_date)
355
+ possible_time = self.nullifyBuffer(possible_time)
356
+ end_found_earlier = False
357
+ time = Dictionary.timePredictionTree
358
+ i = 0
359
+ whitespace_count = 0
360
+ time_frg_length = 0
361
+ continue
362
+ else:
363
+ if count == text_length - 1 and time.explict_date_fragment:
364
+ if Helper.isDigit(c):
365
+ possible_time[time_frg_length] = c
366
+ time_frg_length += 1
367
+ possible_date[i] = c
368
+ i += 1
369
+ date_groups = self.addTimeFragment(
370
+ date_groups, possible_date, possible_time, count, i, time_frg_length, date_time_separator
371
+ )
372
+ search_for_time_piece = False
373
+ possible_date = self.nullifyBuffer(possible_date)
374
+ possible_time = self.nullifyBuffer(possible_time)
375
+ end_found_earlier = False
376
+ time = Dictionary.timePredictionTree
377
+ i = 0
378
+ whitespace_count = 0
379
+ time_frg_length = 0
380
+ continue
381
+ possible_time[time_frg_length] = c
382
+ time_frg_length += 1
383
+ possible_date[i] = c
384
+ i += 1
385
+ continue
386
+ if Helper.isDigit(c) or Helper.isDelimeter(c) or Helper.isTimeSeprator(c):
387
+ if marker == "M":
388
+ if month_determined:
389
+ is_alphanumeric = True
390
+ tree = tree.get_child(marker)
391
+ if tree is None and end_found_earlier:
392
+ continue
393
+ else:
394
+ tree = Dictionary.patternPredictionTree
395
+ possible_date = self.nullifyBuffer(possible_date)
396
+ i = 0
397
+ whitespace_count = 0
398
+ end_found_earlier = False
399
+ month = Dictionary.monthPredictionTree
400
+ marker = "D" if Helper.isDigit(c) else "*"
401
+ if tree is None:
402
+ if end_found_earlier:
403
+ date_groups = self.addDateFragment(
404
+ date_groups, possible_date, count, time_frg_length + i + whitespace_count, is_alphanumeric
405
+ )
406
+ is_alphanumeric = False
407
+ if Helper.isDigit(c):
408
+ time = time.get_child("D")
409
+ if time is not None:
410
+ possible_time[time_frg_length] = c
411
+ time_frg_length += 1
412
+ search_for_time_piece = True
413
+ possible_date[i] = c
414
+ i += 1
415
+ else:
416
+ possible_date = self.nullifyBuffer(possible_date)
417
+ i = 0
418
+ whitespace_count = 0
419
+ end_found_earlier = False
420
+ else:
421
+ possible_date = self.nullifyBuffer(possible_date)
422
+ i = 0
423
+ whitespace_count = 0
424
+ end_found_earlier = False
425
+ end_found_earlier = False
426
+ tree = Dictionary.patternPredictionTree
427
+ month = Dictionary.monthPredictionTree
428
+ continue
429
+ else:
430
+ possible_date = self.nullifyBuffer(possible_date)
431
+ i = 0
432
+ whitespace_count = 0
433
+ end_found_earlier = False
434
+ tree = Dictionary.patternPredictionTree
435
+ month = Dictionary.monthPredictionTree
436
+ continue
437
+ tree = tree.get_child(marker)
438
+ if tree is None:
439
+ if end_found_earlier:
440
+ date_groups = self.addDateFragment(
441
+ date_groups, possible_date, count, time_frg_length + i + whitespace_count, is_alphanumeric
442
+ )
443
+ end_found_earlier = False
444
+ is_alphanumeric = False
445
+ if Helper.isTimeSeprator(c):
446
+ search_for_time_piece = True
447
+ possible_date[i] = c
448
+ i += 1
449
+ date_time_separator = c
450
+ else:
451
+ possible_date = self.nullifyBuffer(possible_date)
452
+ i = 0
453
+ whitespace_count = 0
454
+ tree = Dictionary.patternPredictionTree
455
+ month = Dictionary.monthPredictionTree
456
+ continue
457
+ else:
458
+ possible_date[i] = c
459
+ i += 1
460
+ end_found_earlier = tree.explict_date_fragment
461
+ if count == text_length - 1 and end_found_earlier:
462
+ date_groups = self.addDateFragment(
463
+ date_groups, possible_date, count, time_frg_length + i + whitespace_count, is_alphanumeric
464
+ )
465
+ is_alphanumeric = False
466
+ break
467
+ else:
468
+ marker = "M"
469
+ c_lower = c.lower()
470
+ month = month.get_child(c_lower)
471
+ if month is None:
472
+ if end_found_earlier:
473
+ date_groups = self.addDateFragment(
474
+ date_groups, possible_date, count, time_frg_length + i + whitespace_count, is_alphanumeric
475
+ )
476
+ is_alphanumeric = False
477
+ end_found_earlier = False
478
+ possible_date = self.nullifyBuffer(possible_date)
479
+ i = 0
480
+ whitespace_count = 0
481
+ month = Dictionary.monthPredictionTree
482
+ tree = Dictionary.patternPredictionTree
483
+ month_determined = False
484
+ end_found_earlier = False
485
+ continue
486
+ else:
487
+ month_determined = month.explict_date_fragment
488
+ possible_date[i] = c_lower
489
+ i += 1
490
+ return date_groups
491
+
492
+ def addDateFragment(
493
+ self,
494
+ date_groups: Optional[List[DateElement]],
495
+ possible_date: List[str],
496
+ count: int,
497
+ pattern_length: int,
498
+ is_alphanumeric: bool,
499
+ ) -> Optional[List[DateElement]]:
500
+ if date_groups is None:
501
+ date_groups = []
502
+ date_element = self.createDateFragment(possible_date, count, pattern_length, is_alphanumeric)
503
+ if date_element is not None:
504
+ date_groups.append(date_element)
505
+ return date_groups
506
+
507
+ def isValidTimeFragmentWithEndingDelim(self, possible_time: List[str]) -> bool:
508
+ s = self._buffer_to_string(possible_time)
509
+ original = s
510
+ s = re.sub(r"[0-9]", "", s)
511
+ if 2 < len(s) <= 3:
512
+ if s.startswith("::") and (original.endswith(",") or original.endswith(".")):
513
+ return True
514
+ return False
515
+
516
+ def addTimeFragment(
517
+ self,
518
+ date_groups: Optional[List[DateElement]],
519
+ possible_date: List[str],
520
+ possible_time: List[str],
521
+ count: int,
522
+ i: int,
523
+ time_frg_length: int,
524
+ date_time_separator: str,
525
+ ) -> Optional[List[DateElement]]:
526
+ if not date_groups:
527
+ return date_groups
528
+ ele = date_groups[-1]
529
+ ele.data = self._buffer_to_string(possible_date)
530
+ ele.endPos = count
531
+ ele.timeFragment = self._buffer_to_string(possible_time)
532
+ if time_frg_length >= 2:
533
+ ampm = f"{possible_time[time_frg_length - 2]}{possible_time[time_frg_length - 1]}"
534
+ if ampm.lower() in {"am", "pm"}:
535
+ ele.hasAmPm = True
536
+ ele.dateTimeSeprator = date_time_separator
537
+ return date_groups
538
+
539
+ def createDateFragment(
540
+ self, buffer: List[str], position: int, length: int, is_alphanumeric: bool
541
+ ) -> Optional[DateElement]:
542
+ date_text = self._buffer_to_string(buffer)
543
+ if not date_text:
544
+ return None
545
+ ele = DateElement(date_text)
546
+ length -= 1
547
+ ele.endPos = position
548
+ ele.startPos = position - length
549
+ ele.isAlphaNumeric = is_alphanumeric
550
+ ele.dateFragment = ele.data
551
+ return ele
552
+
553
+ def nullifyBuffer(self, buffer: List[str]) -> List[str]:
554
+ for idx in range(len(buffer)):
555
+ buffer[idx] = "\x00"
556
+ return buffer
557
+
558
+ def _buffer_to_string(self, buffer: List[str]) -> str:
559
+ return "".join(ch for ch in buffer if ch and ch != "\x00").strip()
560
+
561
+ def is31DayMonth(self, value: int) -> bool:
562
+ return value in {1, 3, 5, 7, 8, 10, 12}
563
+
564
+ def is30DayMonth(self, value: int) -> bool:
565
+ return value in {4, 6, 9, 11}
566
+
567
+ def monthToDigit(self, text: str) -> int:
568
+ text = text.lower()
569
+ month_map = {
570
+ "jan": 1,
571
+ "january": 1,
572
+ "feb": 2,
573
+ "february": 2,
574
+ "mar": 3,
575
+ "march": 3,
576
+ "apr": 4,
577
+ "april": 4,
578
+ "may": 5,
579
+ "jun": 6,
580
+ "june": 6,
581
+ "jul": 7,
582
+ "july": 7,
583
+ "aug": 8,
584
+ "august": 8,
585
+ "sep": 9,
586
+ "september": 9,
587
+ "oct": 10,
588
+ "october": 10,
589
+ "nov": 11,
590
+ "november": 11,
591
+ "dec": 12,
592
+ "december": 12,
593
+ }
594
+ return month_map.get(text, -1)
@@ -0,0 +1,37 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+ from typing import List, Optional
5
+
6
+
7
+ @dataclass
8
+ class PredictionModelNode:
9
+ """
10
+ Python equivalent of the Java PredictionModelNode used by the parser's
11
+ dictionary tries.
12
+ """
13
+
14
+ charcter: str = "\x00"
15
+ level: int = 0
16
+ is_digit: bool = False
17
+ explict_date_fragment: bool = False
18
+ parent: Optional["PredictionModelNode"] = None
19
+ childern: List["PredictionModelNode"] = field(default_factory=list)
20
+
21
+ def get_child(self, char: str) -> Optional["PredictionModelNode"]:
22
+ for child in self.childern:
23
+ if child.charcter == char:
24
+ return child
25
+ return None
26
+
27
+ def add_child(self, child: "PredictionModelNode") -> None:
28
+ child.level = self.level + 1
29
+ child.parent = self
30
+ self.childern.append(child)
31
+
32
+ def has_childern(self) -> bool:
33
+ return bool(self.childern)
34
+
35
+ def children_count(self) -> int:
36
+ return len(self.childern)
37
+
File without changes