Spaces:
Sleeping
Sleeping
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' | |
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 | |
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 | |
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 | |
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() | |
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 | |
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 | |
def _count_uppercase_letters(s: str) -> int: | |
""" | |
Считает количество заглавных букв в строке. | |
Args: | |
s (str): Строка для обработки. | |
Returns: | |
int: Количество заглавных букв. | |
""" | |
return len(re.findall(r'[A-Z,А-Я]', s)) | |