1. Защита тегов: Защищенные теги (<code>, <script> и т.д.) теперь физически заменяются на плейсхолдеры (\uFFFC) в DOM-дереве перед обработкой. Это предотвращает "протекание" контекста (например, склеивание слов через код) и защищает содержимое тегов от изменений. 2. Маркеры границ: При сборке "супер-строки" (для контекстной обработки) между всеми текстовыми узлами вставляются специальные разделители (\uFFFF). Это позволяет корректно восстанавливать текст по узлам, даже если длина текста изменилась (например, Unbreakables удалил лишние пробелы). Раньше мы полагались на карту длин (lengths_map), что приводило к смещению текста при любых изменениях длины.
79 lines
4.7 KiB
Python
79 lines
4.7 KiB
Python
# etpgrf/quotes.py
|
||
# Модуль для расстановки кавычек в тексте
|
||
|
||
import regex
|
||
import logging
|
||
from .config import (LANG_RU, LANG_EN, CHAR_RU_QUOT1_OPEN, CHAR_RU_QUOT1_CLOSE, CHAR_EN_QUOT1_OPEN,
|
||
CHAR_EN_QUOT1_CLOSE, CHAR_RU_QUOT2_OPEN, CHAR_RU_QUOT2_CLOSE, CHAR_EN_QUOT2_OPEN,
|
||
CHAR_EN_QUOT2_CLOSE, CHAR_NODE_SEPARATOR)
|
||
from .comutil import parse_and_validate_langs
|
||
|
||
# --- Настройки логирования ---
|
||
logger = logging.getLogger(__name__)
|
||
|
||
# Определяем стили кавычек для разных языков
|
||
# Формат: (('открывающая_ур1', 'закрывающая_ур1'), ('открывающая_ур2', 'закрывающая_ур2'))
|
||
_QUOTE_STYLES = {
|
||
LANG_RU: ((CHAR_RU_QUOT1_OPEN, CHAR_RU_QUOT1_CLOSE), (CHAR_RU_QUOT2_OPEN, CHAR_RU_QUOT2_CLOSE)),
|
||
LANG_EN: ((CHAR_EN_QUOT1_OPEN, CHAR_EN_QUOT1_CLOSE), (CHAR_EN_QUOT2_OPEN, CHAR_EN_QUOT2_CLOSE)),
|
||
}
|
||
|
||
|
||
class QuotesProcessor:
|
||
"""
|
||
Обрабатывает прямые кавычки ("), превращая их в типографские
|
||
в зависимости от языка и контекста.
|
||
"""
|
||
|
||
def __init__(self, langs: str | list[str] | tuple[str, ...] | frozenset[str] | None = None):
|
||
self.langs = parse_and_validate_langs(langs)
|
||
|
||
# Выбираем стиль кавычек на основе первого поддерживаемого языка
|
||
self.open_quote = '"'
|
||
self.close_quote = '"'
|
||
|
||
for lang in self.langs:
|
||
if lang in _QUOTE_STYLES:
|
||
self.open_quote = _QUOTE_STYLES[lang][0][0]
|
||
self.close_quote = _QUOTE_STYLES[lang][0][1]
|
||
logger.debug(
|
||
f"QuotesProcessor: выбран стиль кавычек для языка '{lang}': '{self.open_quote}...{self.close_quote}'")
|
||
break # Используем стиль первого найденного языка
|
||
|
||
# Экранируем разделитель для использования в regex
|
||
sep = regex.escape(CHAR_NODE_SEPARATOR)
|
||
|
||
# Паттерн для открывающей кавычки: " перед буквой/цифрой,
|
||
# которой предшествует пробел, начало строки, открывающая скобка ИЛИ разделитель узлов.
|
||
# (?<=^|\s|[\(\[„\"‘\']|sep) - "просмотр назад" на начало строки... ищет пробел \s или знак из набора ([„"‘' или разделитель
|
||
# (?=\p{L}|sep) - "просмотр вперед" на букву \p{L} (но не цифру) ИЛИ разделитель узлов.
|
||
self._opening_quote_pattern = regex.compile(rf'(?<=^|\s|[\(\[„\"‘\']|{sep})\"(?=\p{{L}}|{sep})')
|
||
# self._opening_quote_pattern = regex.compile(r'(?<=^|\s|\p{Pi}|["\'\(\)])\"(?=\p{L})')
|
||
|
||
# Паттерн для закрывающей кавычки: " после буквы/цифры,
|
||
# за которой следует пробел, пунктуация, конец строки ИЛИ разделитель узлов.
|
||
# (?<=\p{L}|[?!…\.]|sep) - "просмотр назад" на букву или ?!… и точку ИЛИ разделитель узлов.
|
||
# (?=\s|[.,;:!?\)\"»”’]|\Z|sep) - "просмотр вперед" на пробел, пунктуацию, конец строки (\Z) или разделитель.
|
||
self._closing_quote_pattern = regex.compile(rf'(?<=\p{{L}}|[?!…\.]|{sep})\"(?=\s|[\.,;:!?\)\]»”’\"\']|\Z|{sep})')
|
||
# self._closing_quote_pattern = regex.compile(r'(?<=\p{L}|\p{N})\"(?=\s|[\.,;:!?\)\"»”’]|\Z)')
|
||
# self._closing_quote_pattern = regex.compile(r'(?<=\p{L}|[?!…])\"(?=\s|[\p{Po}\p{Pf}"\']|\Z)')
|
||
|
||
def process(self, text: str) -> str:
|
||
"""
|
||
Применяет правила замены кавычек к тексту.
|
||
"""
|
||
if '"' not in text:
|
||
# Быстрый выход, если в тексте нет прямых кавычек
|
||
return text
|
||
|
||
processed_text = text
|
||
|
||
# 1. Заменяем открывающие кавычки
|
||
# Заменяем только найденную кавычку, так как просмотр вперед не захватывает символы.
|
||
processed_text = self._opening_quote_pattern.sub(self.open_quote, processed_text)
|
||
|
||
# 2. Заменяем закрывающие кавычки
|
||
processed_text = self._closing_quote_pattern.sub(self.close_quote, processed_text)
|
||
|
||
return processed_text
|