51 lines
2.8 KiB
Python
51 lines
2.8 KiB
Python
# etpgrf/symbols.py
|
||
# Модуль для преобразования псевдографики в правильные типографские символы.
|
||
|
||
import regex
|
||
import logging
|
||
from .config import CHAR_NDASH, STR_TO_SYMBOL_REPLACEMENTS
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
class SymbolsProcessor:
|
||
"""
|
||
Преобразует ASCII-последовательности (псевдографику) в семантически
|
||
верные Unicode-символы. Работает на раннем этапе, до расстановки пробелов.
|
||
"""
|
||
|
||
def __init__(self):
|
||
# Для сложных замен, требующих анализа контекста (например, диапазоны),
|
||
# по-прежнему используем регулярные выражения.
|
||
# Паттерн для диапазонов: цифра-дефис-цифра -> цифра–цифра (среднее тире).
|
||
# Обрабатываем арабские и римские цифры.
|
||
self._range_pattern = regex.compile(pattern=r'(\d)-(\d)|([IVXLCDM]+)-([IVXLCDM]+)', flags=regex.IGNORECASE)
|
||
|
||
logger.debug("SymbolsProcessor `__init__`")
|
||
|
||
def _replace_range(self, match: regex.Match) -> str:
|
||
# Паттерн имеет две группы: (\d)-(\d) ИЛИ ([IVX...])-([IVX...])
|
||
if match.group(1) is not None: # Арабские цифры
|
||
return f'{match.group(1)}{CHAR_NDASH}{match.group(2)}'
|
||
if match.group(3) is not None: # Римские цифры
|
||
return f'{match.group(3)}{CHAR_NDASH}{match.group(4)}'
|
||
return match.group(0) # На всякий случай
|
||
|
||
|
||
def process(self, text: str) -> str:
|
||
# Шаг 1: Выполняем простые замены из списка `STR_TO_SYMBOL_REPLACEMENTS` (см. config.py).
|
||
# Этот шаг должен идти первым, чтобы пользователь мог, например,
|
||
# использовать '---' в диапазоне '1---5', если ему это нужно.
|
||
# В таком случае '---' заменится на '—', и правило для диапазонов
|
||
# с дефисом уже не сработает.
|
||
processed_text = text
|
||
for old, new in STR_TO_SYMBOL_REPLACEMENTS:
|
||
processed_text = processed_text.replace(old, new)
|
||
|
||
# Шаг 2: Обрабатываем диапазоны с помощью регулярного выражения.
|
||
# Эта замена более специфична и требует контекста (цифры вокруг дефиса).
|
||
processed_text = self._range_pattern.sub(self._replace_range, processed_text)
|
||
|
||
return processed_text
|
||
|