import re from rutextnorm import normalize_russian from app.ssml import PAUSE_DELIM _TRAILING_CLOSERS = "\"'»)]`" # Региональные (языкоспецифичные) нормализаторы: lang -> функция. # Общие шаги, применимые к любому языку, вынесены в normalize_common(). _REGIONAL_NORMALIZERS = {"ru": normalize_russian} _DELIM_CHARS = "([{«“<)]}»>\"'`*_~" _DELIM_RUN_RE = re.compile(r"([ \t]*)([" + re.escape(_DELIM_CHARS) + r"]+)([ \t]*)") def _prev_nonspace(text: str, pos: int) -> str: i = pos - 1 while i >= 0 and text[i] in " \t": i -= 1 return text[i] if i >= 0 else "" def _next_nonspace(text: str, pos: int) -> str: while pos < len(text) and text[pos] in " \t": pos += 1 return text[pos] if pos < len(text) else "" def mark_pause_delimiters(text: str) -> str: """Удаляет выделительные разделители (скобки, кавычки, markdown-разметку). Если по обе стороны от прогона есть буквы/цифры, ставит sentinel паузы (паузная граница для SSML-слоя) вместо литеральной запятой; иначе просто удаляет. Одинарная кавычка между буквами (апостроф) сохраняется.""" def repl(m: re.Match) -> str: left, run, right = m.group(1), m.group(2), m.group(3) start, end = m.start(2), m.end(2) left_touch = text[start - 1] if start > 0 else "" right_touch = text[end] if end < len(text) else "" if set(run) == {"'"} and left_touch.isalnum() and right_touch.isalnum(): return m.group(0) if _prev_nonspace(text, start).isalnum() and _next_nonspace(text, end).isalnum(): return " " + PAUSE_DELIM + " " return " " if (left or right) else "" return _DELIM_RUN_RE.sub(repl, text) def ensure_line_punctuation(text: str) -> str: result = [] for line in text.split("\n"): line = line.rstrip() core = line.rstrip(_TRAILING_CLOSERS).rstrip() if core and core[-1].isalnum(): line = line + "." result.append(line) return "\n".join(result) def normalize_common(text: str) -> str: """Языконезависимая нормализация. Применяется ко всему тексту целиком, до разбиения на предложения, для любого языка.""" text = mark_pause_delimiters(text) text = ensure_line_punctuation(text) text = re.sub(r"[ \t]{2,}", " ", text) return text def normalize_language(text: str, lang: str) -> str: """Региональная нормализация для конкретного языка. Без изменений для языков, у которых нет собственных правил (например, en).""" fn = _REGIONAL_NORMALIZERS.get(lang) return fn(text) if fn else text def normalize_ru(text: str) -> str: return normalize_language(text, "ru")