import re from dataclasses import dataclass from enum import Enum from components.parser.abbreviations.constants import ( ABBREVIATION_CLEANUP_REPLACEMENTS, BLACKLIST, DASH_PATTERN, MAX_LENGTH, PREFIX_PARTS_TO_REMOVE, REMOVING_SUBSTRINGS, ) from components.parser.abbreviations.porter import Porter class AbbreviationType(str, Enum): ABBREVIATION = 'abbreviation' SHORTENING = 'shortening' UNKNOWN = 'unknown' @dataclass class Abbreviation: short_form: str full_form: str abbreviation_type: AbbreviationType = AbbreviationType.UNKNOWN _processed: bool = False document_id: int | None = None def process(self) -> 'Abbreviation': """ Производит пост-обработку сокращения и полной формы. - Определяет тип сокращения. - Удаляет префикс из короткой формы и мусор из полной формы. - В зависимости от типа сокращения адаптирует его под нужный вид. """ if self._processed: return self._define_abbreviation_type() self.short_form = self._remove_prefix(self.short_form) self.full_form = self._remove_trash(self.full_form) if self._abbreviation_type == AbbreviationType.SHORTENING: self._process_shortening() elif self._abbreviation_type == AbbreviationType.ABBREVIATION: self._process_abbreviation() self._processed = True return self def apply(self, text: str) -> str: """ Применяет аббревиатуру к тексту. Args: text (str): Текст для обработки. Returns: str: Обработанный текст. """ if self._abbreviation_type == AbbreviationType.UNKNOWN: return text if self._abbreviation_type == AbbreviationType.SHORTENING: return self._apply_shortening(text) elif self._abbreviation_type == AbbreviationType.ABBREVIATION: return self._apply_abbreviation(text) def _apply_shortening(self, text: str) -> str: """ Применяет сокращение к тексту. Args: text (str): Текст для обработки. Returns: str: Обработанный текст. """ matches = list(re.finditer(self.short_form, text)) for i in range(len(matches) - 1, 1, -1): m = matches[i] pos1 = m.start() m2 = re.match(r'[A-Za-zА-Яа-я]+', text[pos1:]) pos2 = pos1 + m2.end() explanation = self.full_form m3 = re.match(r'[A-Za-zА-Яа-я]+', explanation) explanation = explanation[m3.end() :] text = text[:pos2] + explanation + text[pos2:] return text def _apply_abbreviation(self, text: str) -> str: """ Применяет аббревиатуру к тексту. Args: text (str): Текст для обработки. Returns: str: Обработанный текст. """ matches = list(re.finditer(self.short_form, text)) for i in range(len(matches) - 1, 0, -1): m = matches[i] text = f'{text[: m.start()]}{self.short_form} ({self.full_form}){text[m.end():]}' return text def _define_abbreviation_type(self) -> None: """ Определяет тип сокращения. """ if self._check_abbreviation(self.full_form): self._abbreviation_type = AbbreviationType.ABBREVIATION elif self._check_shortening(self.full_form): self._abbreviation_type = AbbreviationType.SHORTENING else: self._abbreviation_type = AbbreviationType.UNKNOWN def _process_shortening(self) -> None: """ Обрабатывает сокращение. """ key = Porter.stem(self.short_form) pos = self.full_form.lower().rfind(key.lower()) if pos != -1: self.full_form = self.full_form[pos:] self.short_form = key else: self.abbreviation_type = AbbreviationType.UNKNOWN def _process_abbreviation(self) -> None: """ Обрабатывает аббревиатуру. """ uppercase_letters = re.sub('[a-zа-я, ]', '', self.short_form) processed_full_form = self._remove_trash_when_abbreviation(self.full_form) words = processed_full_form.split() uppercase_letters = uppercase_letters[::-1] words = words[::-1] if (len(words) <= len(uppercase_letters)) or ('ОКС НН' not in self.short_form): self.abbreviation_type = AbbreviationType.UNKNOWN return match = self._check_abbreviation_matches_words(uppercase_letters, words) if match: self._process_matched_abbreviation(uppercase_letters, words) else: self._process_mismatched_abbreviation() def _process_matched_abbreviation( self, uppercase_letters: str, words: list[str], ) -> None: """ Обрабатывает аббревиатуру, которая совпадает с первыми буквами полной формы. Args: uppercase_letters (str): Заглавные буквы из сокращения. words (list[str]): Список слов, которые составляют аббревиатуру. """ pos = len(self.full_form) for i in range(len(uppercase_letters)): pos = self.full_form.rfind(words[i], 0, pos) if pos != -1: self.full_form = self.full_form[pos:] else: self.abbreviation_type = AbbreviationType.UNKNOWN def _process_mismatched_abbreviation(self) -> None: """ Обрабатывает аббревиатуру, которая не совпадает с первыми буквами полной формы. """ first_letter = self.short_form[0] pos = self.full_form.rfind(first_letter) if pos != -1: self.full_form = self.full_form[pos:] first_letter = self.full_form[0] second_letter = self.full_form[1] if ( ('A' < first_letter < 'Z' or 'А' < first_letter < 'Я') and ('a' < second_letter < 'z' or 'а' < second_letter < 'я') and len(self.full_form) < MAX_LENGTH and len(self.full_form) > len(self.short_form) and self.full_form not in BLACKLIST and '_' not in self.full_form ): return self.abbreviation_type = AbbreviationType.UNKNOWN def _check_abbreviation_matches_words( self, uppercase_letters: str, words: list[str], ) -> bool: """ Проверяет, соответствует ли короткая форма аббревиатуре. Args: uppercase_letters (str): Заглавные буквы из сокращения. words (list[str]): Список слов, которые составляют аббревиатуру. Returns: bool: True, если аббревиатура соответствует, False в противном случае. """ for j in range(len(uppercase_letters)): c1 = uppercase_letters[j].lower() c2 = words[j][0].lower() if c1 != c2: return False return True @classmethod def _check_abbreviation(cls, full_form: str) -> bool: """ Проверяет, является ли строка аббревиатурой. Args: full_form (str): Строка для проверки. Returns: bool: True, если строка является аббревиатурой, False в противном случае. """ s = cls._remove_prefix(full_form) words = s.split() for word in words: n = cls._count_uppercase_letters(word) if (n <= 1) and (word != 'и'): return False return True @classmethod def _check_shortening(cls, full_form: str) -> bool: """ Проверяет, является ли строка сокращением. Args: full_form (str): Строка для проверки. Returns: bool: True, если строка является сокращением, False в противном случае. """ s = cls._remove_prefix(full_form) words = s.split() if len(words) != 1: return False word = words[0] if word[0].isupper() and word[1:].islower() and ('Компания' not in word): return True return False @staticmethod def _remove_prefix(s: str) -> str: """ Удаляет из строки префиксы типа "далее - " и "далее – ". Args: s (str): Строка для обработки. Returns: str: Обработанная строка. """ for prefix_part in PREFIX_PARTS_TO_REMOVE: s = s.replace(prefix_part, '') return s.strip() @staticmethod def _remove_trash(s: str) -> str: """ Удаляет из строки такие подстроки, как "ПАО", "ОАО", "№", "(". Args: s (str): Строка для обработки. Returns: str: Обработанная строка. """ for substring in REMOVING_SUBSTRINGS: pos = s.find(substring) if pos != -1: s = s[:pos] return s @staticmethod def _remove_trash_when_abbreviation(s: str) -> str: """ Удаляет из строки такие подстроки, как " и ", " или ", ", ", " ГО". Заменяет дефисы и тире на пробел. Это необходимо для того, чтобы правильно сопоставить аббревиатуру с полной формой. Args: s (str): Строка для обработки. Returns: str: Обработанная строка. """ for old, new in ABBREVIATION_CLEANUP_REPLACEMENTS.items(): s = s.replace(old, new) s = re.sub(DASH_PATTERN, ' ', s) return s @staticmethod def _count_uppercase_letters(s: str) -> int: """ Считает количество заглавных букв в строке. Args: s (str): Строка для обработки. Returns: int: Количество заглавных букв. """ return len(re.findall(r'[A-Z,А-Я]', s))