muryshev's picture
init
57cf043
raw
history blame
11.3 kB
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))