add: LayoutProcessor - обработка едениц измерения (кажется все)
This commit is contained in:
@@ -63,6 +63,10 @@ CHAR_ARROW_LR = '\u27f7' # Длинная двунаправленная ст
|
|||||||
CHAR_ARROW_L_LONG_DOUBLE = '\u27f8' # Длинная двойная стрелка влево
|
CHAR_ARROW_L_LONG_DOUBLE = '\u27f8' # Длинная двойная стрелка влево
|
||||||
CHAR_ARROW_R_LONG_DOUBLE = '\u27f9' # Длинная двойная стрелка вправо
|
CHAR_ARROW_R_LONG_DOUBLE = '\u27f9' # Длинная двойная стрелка вправо
|
||||||
CHAR_ARROW_LR_LONG_DOUBLE = '\u27fa' # Длинная двойная двунаправленная стрелка
|
CHAR_ARROW_LR_LONG_DOUBLE = '\u27fa' # Длинная двойная двунаправленная стрелка
|
||||||
|
CHAR_MIDDOT = '\u00b7' # Средняя точка (· иногда используется как знак умножения) / ·
|
||||||
|
CHAR_UNIT_SEPARATOR = '\u25F0' # Символ временный разделитель для составных единиц (◰), чтобы не уходить
|
||||||
|
# в "мертвый" цикл при замене на тонкий пробел. Можно взять любой редкий символом.
|
||||||
|
|
||||||
|
|
||||||
# === КОНСТАНТЫ ПСЕВДОГРАФИКИ ===
|
# === КОНСТАНТЫ ПСЕВДОГРАФИКИ ===
|
||||||
# Для простых замен "строка -> символ" используем список кортежей.
|
# Для простых замен "строка -> символ" используем список кортежей.
|
||||||
@@ -92,28 +96,6 @@ STR_TO_SYMBOL_REPLACEMENTS = [
|
|||||||
('~=', CHAR_AP), # Приблизительно равно (≈)
|
('~=', CHAR_AP), # Приблизительно равно (≈)
|
||||||
]
|
]
|
||||||
|
|
||||||
# === КОНСТАНТЫ ДЛЯ ЕДИНИЦ ИЗМЕРЕНИЯ ===
|
|
||||||
# Пост-позиционные (10 км)
|
|
||||||
DEFAULT_POST_UNITS = [
|
|
||||||
# Русские
|
|
||||||
'гг', 'г.', 'кг', 'мг', 'ц', 'т',
|
|
||||||
'кв.м', 'куб.м', 'мм', 'см', 'м', 'км', 'л', 'мл', 'сот', 'га',
|
|
||||||
'сек', 'с.', 'мин', 'ч',
|
|
||||||
'руб', 'коп',
|
|
||||||
'тыс', 'млн', 'млрд',
|
|
||||||
'пп', 'стр', 'рис', 'табл', 'гл', 'п', 'шт',
|
|
||||||
# Английские
|
|
||||||
'pp', 'p', 'para', 'sect', 'fig', 'vol', 'ed',
|
|
||||||
]
|
|
||||||
# Пред-позиционные (№ 5, $ 10)
|
|
||||||
DEFAULT_PRE_UNITS = ['№', '$', '€', '£', '₽', '#']
|
|
||||||
|
|
||||||
# === КОНСТАНТЫ ДЛЯ СЛОЖНЫХ (СОСТАВНЫХ) ЕДИНИЦ ИЗМЕРЕНИЯ ===
|
|
||||||
# Эти единицы будут автоматически "склеены" неразрывными пробелами внутри LayoutProcessor
|
|
||||||
DEFAULT_COMPLEX_UNITS = [
|
|
||||||
'до н. э.',
|
|
||||||
'н. э.',
|
|
||||||
]
|
|
||||||
|
|
||||||
# === КОНСТАНТЫ ДЛЯ КОДИРОВАНИЯ HTML-МНЕМНОИКОВ ===
|
# === КОНСТАНТЫ ДЛЯ КОДИРОВАНИЯ HTML-МНЕМНОИКОВ ===
|
||||||
# --- ЧЕРНЫЙ СПИСОК: Символы, которые НИКОГДА не нужно кодировать в мнемоники ---
|
# --- ЧЕРНЫЙ СПИСОК: Символы, которые НИКОГДА не нужно кодировать в мнемоники ---
|
||||||
@@ -185,7 +167,7 @@ CUSTOM_ENCODE_MAP = {
|
|||||||
'\u0026': '&', # & / & / &
|
'\u0026': '&', # & / & / &
|
||||||
'\u003e': '>', # > / > / >
|
'\u003e': '>', # > / > / >
|
||||||
'\u003c': '<', # < / < / <
|
'\u003c': '<', # < / < / <
|
||||||
'\u00b7': '·', # · / · / · / ·
|
CHAR_MIDDOT: '·', # · / · / · / ·
|
||||||
'\u0060': '`', # ` / ` / `
|
'\u0060': '`', # ` / ` / `
|
||||||
'\u00a8': '¨', # ¨ / ¨ / ¨ / ¨ / ¨
|
'\u00a8': '¨', # ¨ / ¨ / ¨ / ¨ / ¨
|
||||||
'\u00b1': '±', # ± / ± / ±
|
'\u00b1': '±', # ± / ± / ±
|
||||||
@@ -638,3 +620,27 @@ ENCODE_MAP = _build_translation_maps()
|
|||||||
def get_encode_map():
|
def get_encode_map():
|
||||||
"""Возвращает готовую карту для кодирования."""
|
"""Возвращает готовую карту для кодирования."""
|
||||||
return ENCODE_MAP
|
return ENCODE_MAP
|
||||||
|
|
||||||
|
|
||||||
|
# === КОНСТАНТЫ ДЛЯ ЕДИНИЦ ИЗМЕРЕНИЯ ===
|
||||||
|
# ТОЛЬКО АТОМАРНЫЕ единицы измерения: 'г', 'м', 'с', 'км', 'кв', 'куб', 'ч' и так далее.
|
||||||
|
# Никаких сложных и составных, типа: 'кв.м.', 'км/ч' или "до н.э." ...
|
||||||
|
# Пост-позиционные (10 км).
|
||||||
|
DEFAULT_POST_UNITS = [
|
||||||
|
# Русские
|
||||||
|
'гг', 'г.', 'в.', 'вв', 'н', 'э',
|
||||||
|
'кг', 'мг', 'ц', 'т',
|
||||||
|
'кв', 'куб', 'мм', 'см', 'м', 'км', 'л', 'мл', 'сот', 'га',
|
||||||
|
'сек', 'с.', 'мин', 'ч',
|
||||||
|
'руб', 'коп',
|
||||||
|
'тыс', 'млн', 'млрд', 'трлн', 'трлрд',
|
||||||
|
'пп', 'стр', 'рис', 'табл', 'гл', 'п', 'шт', 'об'
|
||||||
|
# Английские
|
||||||
|
'pp', 'p', 'para', 'sect', 'fig', 'vol', 'ed',
|
||||||
|
]
|
||||||
|
# Пред-позиционные (№ 5, $ 10)
|
||||||
|
DEFAULT_PRE_UNITS = ['№', '$', '€', '£', '₽', '#']
|
||||||
|
|
||||||
|
# Операторы, которые могут стоять между единицами измерения (км/ч)
|
||||||
|
# Сложение и вычитание здесь намеренно отсутствуют.
|
||||||
|
UNIT_MATH_OPERATORS = ['/', '*', '×', CHAR_MIDDOT, '÷']
|
@@ -3,8 +3,8 @@
|
|||||||
|
|
||||||
import regex
|
import regex
|
||||||
import logging
|
import logging
|
||||||
from etpgrf.config import (LANG_RU, LANG_EN, CHAR_NBSP, CHAR_THIN_SP, CHAR_NDASH, CHAR_MDASH, CHAR_HELLIP,
|
from etpgrf.config import (LANG_RU, LANG_EN, CHAR_NBSP, CHAR_THIN_SP, CHAR_NDASH, CHAR_MDASH, CHAR_HELLIP, CHAR_UNIT_SEPARATOR,
|
||||||
DEFAULT_POST_UNITS, DEFAULT_PRE_UNITS, DEFAULT_COMPLEX_UNITS)
|
DEFAULT_POST_UNITS, DEFAULT_PRE_UNITS, UNIT_MATH_OPERATORS)
|
||||||
from etpgrf.comutil import parse_and_validate_langs
|
from etpgrf.comutil import parse_and_validate_langs
|
||||||
|
|
||||||
# --
|
# --
|
||||||
@@ -25,14 +25,12 @@ class LayoutProcessor:
|
|||||||
def __init__(self,
|
def __init__(self,
|
||||||
langs: str | list[str] | tuple[str, ...] | frozenset[str] | None = None,
|
langs: str | list[str] | tuple[str, ...] | frozenset[str] | None = None,
|
||||||
process_initials_and_acronyms: bool = True,
|
process_initials_and_acronyms: bool = True,
|
||||||
process_units: bool | str | list[str] = True,
|
process_units: bool | str | list[str] = True):
|
||||||
process_complex_units: bool | list[str] = True):
|
|
||||||
|
|
||||||
self.langs = parse_and_validate_langs(langs)
|
self.langs = parse_and_validate_langs(langs)
|
||||||
self.main_lang = self.langs[0] if self.langs else LANG_RU
|
self.main_lang = self.langs[0] if self.langs else LANG_RU
|
||||||
self.process_initials_and_acronyms = process_initials_and_acronyms
|
self.process_initials_and_acronyms = process_initials_and_acronyms
|
||||||
self.process_units = process_units
|
self.process_units = process_units
|
||||||
self.process_complex_units = process_complex_units
|
|
||||||
# 1. Паттерн для длинного (—) или среднего (–) тире, окруженного пробелами.
|
# 1. Паттерн для длинного (—) или среднего (–) тире, окруженного пробелами.
|
||||||
# (?<=[\p{L}\p{Po}\p{Pf}"\']) - просмотр назад на букву, пунктуацию или кавычку.
|
# (?<=[\p{L}\p{Po}\p{Pf}"\']) - просмотр назад на букву, пунктуацию или кавычку.
|
||||||
self._dash_pattern = regex.compile(rf'(?<=[\p{{L}}\p{{Po}}\p{{Pf}}"\'])\s+([{CHAR_MDASH}{CHAR_NDASH}])\s+(?=\S)')
|
self._dash_pattern = regex.compile(rf'(?<=[\p{{L}}\p{{Po}}\p{{Pf}}"\'])\s+([{CHAR_MDASH}{CHAR_NDASH}])\s+(?=\S)')
|
||||||
@@ -62,59 +60,49 @@ class LayoutProcessor:
|
|||||||
self._initial_to_initial_ns_pattern = regex.compile(r'(\p{Lu}\.)(?=\p{Lu}\.)')
|
self._initial_to_initial_ns_pattern = regex.compile(r'(\p{Lu}\.)(?=\p{Lu}\.)')
|
||||||
self._initial_to_surname_ns_pattern = regex.compile(r'(\p{Lu}\.)(?=\p{Lu}\p{L}{1,})')
|
self._initial_to_surname_ns_pattern = regex.compile(r'(\p{Lu}\.)(?=\p{Lu}\p{L}{1,})')
|
||||||
|
|
||||||
# 5. Паттерны для единиц измерения.
|
# Паттерн, описывающий "число" - арабское (включая дроби) ИЛИ римское.
|
||||||
|
# Для римских цифр используется \b, чтобы не спутать 'I' с частью слова.
|
||||||
|
self._NUMBER_PATTERN = r'(?:\d[\d.,]*|\b[IVXLCDM]+\b)'
|
||||||
|
|
||||||
|
# 5. Паттерны для единиц измерения (простые и составные).
|
||||||
self._post_units_pattern = None
|
self._post_units_pattern = None
|
||||||
self._pre_units_pattern = None
|
self._pre_units_pattern = None
|
||||||
|
self._complex_unit_pattern = None
|
||||||
|
self._math_unit_pattern = None
|
||||||
if self.process_units:
|
if self.process_units:
|
||||||
post_units = list(DEFAULT_POST_UNITS)
|
all_post_units = list(DEFAULT_POST_UNITS)
|
||||||
pre_units = list(DEFAULT_PRE_UNITS)
|
|
||||||
# Проверяем и добавляем пользовательские единицы измерения
|
|
||||||
custom_units = []
|
|
||||||
|
|
||||||
# Обработка составных единиц: "склеиваем" их тонкой шпацией и добавляем в общий список
|
|
||||||
if self.process_complex_units:
|
|
||||||
complex_units_to_process = list(DEFAULT_COMPLEX_UNITS)
|
|
||||||
if isinstance(self.process_complex_units, (list, tuple, set)):
|
|
||||||
complex_units_to_process.extend(self.process_complex_units)
|
|
||||||
|
|
||||||
# "Склеиваем" пробелы внутри составных единиц и добавляем в общий список
|
|
||||||
post_units.extend([unit.replace(' ', CHAR_THIN_SP) for unit in complex_units_to_process])
|
|
||||||
|
|
||||||
if isinstance(self.process_units, str):
|
if isinstance(self.process_units, str):
|
||||||
# Если кастомные единицы заданы строкой, разбиваем по пробелам
|
all_post_units.extend(self.process_units.split())
|
||||||
custom_units = self.process_units.split()
|
|
||||||
elif isinstance(self.process_units, (list, tuple, set)):
|
elif isinstance(self.process_units, (list, tuple, set)):
|
||||||
# Если кастомные единицы заданы списком/кортежем/множеством, просто конвертируем в список
|
all_post_units.extend(self.process_units)
|
||||||
custom_units = list(self.process_units)
|
|
||||||
|
|
||||||
if custom_units:
|
units_pattern_part = ''
|
||||||
post_units.extend(custom_units)
|
|
||||||
|
|
||||||
if post_units:
|
# Общий паттерн для всех остальных единиц
|
||||||
# [\d.,]+ - число, возможно, с точкой или запятой
|
if all_post_units:
|
||||||
# Используем негативный просмотр вперед (?!), чтобы убедиться, что за единицей
|
sorted_units = sorted(all_post_units, key=len, reverse=True)
|
||||||
# не следует другая буква. Это надежнее, чем \b, особенно для единиц,
|
units_pattern_part = '|'.join(map(regex.escape, sorted_units))
|
||||||
# оканчивающихся на точку (например, "г.").
|
|
||||||
post_pattern_str = r'(\d[\d.,]*)\s+(' + '|'.join(regex.escape(u) for u in post_units) + r')(?![\p{L}\p{N}])'
|
|
||||||
self._post_units_pattern = regex.compile(post_pattern_str)
|
|
||||||
|
|
||||||
if pre_units:
|
if units_pattern_part:
|
||||||
# Используем негативный просмотр назад (?<!), чтобы убедиться, что перед единицей
|
# Простые единицы: число + единица
|
||||||
# нет буквы. \b здесь не работает для символов типа "№" или "$".
|
self._post_units_pattern = regex.compile(rf'({self._NUMBER_PATTERN})\s+({units_pattern_part})(?!\w)')
|
||||||
pre_pattern_str = r'(?<![\p{L}\p{N}])(' + '|'.join(regex.escape(u) for u in pre_units) + r')\s+(\d[\d.,]*)'
|
# Паттерн для составных единиц: ищет пару "единица." + "единица", разделенную пробелами (или без них).
|
||||||
self._pre_units_pattern = regex.compile(pre_pattern_str)
|
# Обязательное наличие точки `\.` после первой единицы делает цикл обработки безопасным.
|
||||||
|
self._complex_unit_pattern = regex.compile(r'\b(' + units_pattern_part + r')\.(\s*)(' + units_pattern_part + r')(?!\w)')
|
||||||
|
# Паттерн для математических операций между единицами
|
||||||
|
math_ops_pattern = '|'.join(map(regex.escape, UNIT_MATH_OPERATORS))
|
||||||
|
self._math_unit_pattern = regex.compile(
|
||||||
|
r'\b(' + units_pattern_part + r')\s*(' + math_ops_pattern + r')\s*(' + units_pattern_part + r')(?!\w)')
|
||||||
|
|
||||||
# 6. Паттерн для связи единиц-умножителей (тыс., млн.) со следующей единицей.
|
# Паттерн для пред-позиционных единиц
|
||||||
# Ищет умножитель, за которым может быть точка, а затем пробел.
|
self._pre_units_pattern = regex.compile(
|
||||||
multiplier_units = ['тыс', 'млн', 'млрд']
|
r'(?<![\p{L}\p{N}])(' + '|'.join(map(regex.escape, DEFAULT_PRE_UNITS)) + rf')\s+({self._NUMBER_PATTERN})')
|
||||||
self._unit_multiplier_pattern = regex.compile(r'((' + '|'.join(multiplier_units) + r')\.?)\s+')
|
|
||||||
|
|
||||||
logger.debug(f"LayoutProcessor `__init__`. "
|
logger.debug(f"LayoutProcessor `__init__`. "
|
||||||
f"Langs: {self.langs}, "
|
f"Langs: {self.langs}, "
|
||||||
f"Main lang: {self.main_lang}, "
|
f"Main lang: {self.main_lang}, "
|
||||||
f"Process initials and acronyms: {self.process_initials_and_acronyms}, "
|
f"Process initials and acronyms: {self.process_initials_and_acronyms}, "
|
||||||
f"Process units: {bool(self.process_units)}, "
|
f"Process units: {bool(self.process_units)}")
|
||||||
f"Process complex units: {bool(self.process_complex_units)}")
|
|
||||||
|
|
||||||
def _replace_dash_spacing(self, match: regex.Match) -> str:
|
def _replace_dash_spacing(self, match: regex.Match) -> str:
|
||||||
"""Callback-функция для расстановки пробелов вокруг тире с учетом языка."""
|
"""Callback-функция для расстановки пробелов вокруг тире с учетом языка."""
|
||||||
@@ -152,15 +140,24 @@ class LayoutProcessor:
|
|||||||
processed_text = self._surname_to_initial_ws_pattern.sub(f'\\1{CHAR_NBSP}', processed_text)
|
processed_text = self._surname_to_initial_ws_pattern.sub(f'\\1{CHAR_NBSP}', processed_text)
|
||||||
|
|
||||||
# 5. Обработка единиц измерения (если включено).
|
# 5. Обработка единиц измерения (если включено).
|
||||||
if self.process_units and self._unit_multiplier_pattern:
|
|
||||||
processed_text = self._unit_multiplier_pattern.sub(r'\1' + CHAR_NBSP, processed_text)
|
|
||||||
|
|
||||||
# 6. Обработка единиц измерения (простых и составных).
|
|
||||||
if self.process_units:
|
if self.process_units:
|
||||||
|
if self._complex_unit_pattern:
|
||||||
|
# Шаг 1: "Склеиваем" все составные единицы с помощью временного разделителя.
|
||||||
|
# Цикл безопасен, так как мы заменяем пробелы на непробельный символ, и паттерн не найдет себя снова.
|
||||||
|
while self._complex_unit_pattern.search(processed_text):
|
||||||
|
processed_text = self._complex_unit_pattern.sub(
|
||||||
|
fr'\1.{CHAR_UNIT_SEPARATOR}\3', processed_text, count=1)
|
||||||
|
|
||||||
|
if self._math_unit_pattern:
|
||||||
|
# processed_text = self._math_unit_pattern.sub(r'\1/\2', processed_text)
|
||||||
|
processed_text = self._math_unit_pattern.sub(r'\1\2\3', processed_text)
|
||||||
|
# И только потом привязываем простые единицы к числам
|
||||||
if self._post_units_pattern:
|
if self._post_units_pattern:
|
||||||
processed_text = self._post_units_pattern.sub(f'\\1{CHAR_NBSP}\\2', processed_text)
|
processed_text = self._post_units_pattern.sub(f'\\1{CHAR_NBSP}\\2', processed_text)
|
||||||
if self._pre_units_pattern:
|
if self._pre_units_pattern:
|
||||||
processed_text = self._pre_units_pattern.sub(f'\\1{CHAR_NBSP}\\2', processed_text)
|
processed_text = self._pre_units_pattern.sub(f'\\1{CHAR_NBSP}\\2', processed_text)
|
||||||
|
|
||||||
|
# Шаг 2: Заменяем все временные разделители на правильную тонкую шпацию.
|
||||||
|
processed_text = processed_text.replace(CHAR_UNIT_SEPARATOR, CHAR_THIN_SP)
|
||||||
|
|
||||||
return processed_text
|
return processed_text
|
||||||
|
@@ -47,7 +47,7 @@ _RU_OLD_POSTPOSITIVE_PARTICLES = frozenset([
|
|||||||
])
|
])
|
||||||
|
|
||||||
_EN_UNBREAKABLE_WORDS = frozenset([
|
_EN_UNBREAKABLE_WORDS = frozenset([
|
||||||
# 1-2 letter words
|
# 1-2 letter words (I - as pronoun)
|
||||||
'a', 'an', 'as', 'at', 'by', 'in', 'is', 'it', 'of', 'on', 'or', 'so', 'to', 'if',
|
'a', 'an', 'as', 'at', 'by', 'in', 'is', 'it', 'of', 'on', 'or', 'so', 'to', 'if',
|
||||||
# 3-4 letter words
|
# 3-4 letter words
|
||||||
'for', 'from', 'into', 'that', 'then', 'they', 'this', 'was', 'were', 'what', 'when', 'with',
|
'for', 'from', 'into', 'that', 'then', 'they', 'this', 'was', 'were', 'what', 'when', 'with',
|
||||||
|
@@ -98,16 +98,34 @@ LAYOUT_TEST_CASES = [
|
|||||||
# --- Единицы измерения (по умолчанию) ---
|
# --- Единицы измерения (по умолчанию) ---
|
||||||
('ru', "Радиус Солнца — около 696.340 км", f"Радиус Солнца{CHAR_NBSP}— около 696.340{CHAR_NBSP}км"),
|
('ru', "Радиус Солнца — около 696.340 км", f"Радиус Солнца{CHAR_NBSP}— около 696.340{CHAR_NBSP}км"),
|
||||||
('ru', "5 кг.", f"5{CHAR_NBSP}кг."),
|
('ru', "5 кг.", f"5{CHAR_NBSP}кг."),
|
||||||
('ru', "Доработки проекта стоили 100 тыс. руб.", f"Доработки проекта стоили 100{CHAR_NBSP}тыс.{CHAR_NBSP}руб."),
|
('ru', "Доработки проекта стоили 100 тыс. руб.", f"Доработки проекта стоили 100{CHAR_NBSP}тыс.{CHAR_THIN_SP}руб."),
|
||||||
('ru', "№ 5", f"№{CHAR_NBSP}5"),
|
('ru', "№ 5", f"№{CHAR_NBSP}5"),
|
||||||
('ru', "Договор № 504/2025А", f"Договор №{CHAR_NBSP}504/2025А"),
|
('ru', "Договор № 504/2025А", f"Договор №{CHAR_NBSP}504/2025А"),
|
||||||
('ru+en', "Доплата за багаж $ 45.50", f"Доплата за багаж ${CHAR_NBSP}45.50"),
|
('ru+en', "Доплата за багаж $ 45.50", f"Доплата за багаж ${CHAR_NBSP}45.50"),
|
||||||
('ru+en', "Инвестиции составили $2.5 млн.", f"Инвестиции составили $2.5{CHAR_NBSP}млн."),
|
('ru+en', "Инвестиции составили $2.5 млн.", f"Инвестиции составили $2.5{CHAR_NBSP}млн."),
|
||||||
|
('ru+en', "Инвестиции составили $ 2.5 млн.", f"Инвестиции составили ${CHAR_NBSP}2.5{CHAR_NBSP}млн."),
|
||||||
# Сложные единицы (склеиваются тонкой шпацией, привязываются к числу неразрывным пробелом)
|
# Сложные единицы (склеиваются тонкой шпацией, привязываются к числу неразрывным пробелом)
|
||||||
('ru', "Дом 120 кв.м. / Участок 6 сот.", f"Дом 120{CHAR_NBSP}кв.м. / Участок 6{CHAR_NBSP}сот."),
|
# ('ru', "Дом 120 кв.м. / Участок 6 сот.", f"Дом 120{CHAR_NBSP}кв.м. / Участок 6{CHAR_NBSP}сот."),
|
||||||
('ru', "Гробик кладут в ямку 2 кв. м.", f"Гробик кладут в ямку 2 кв. м."),
|
# ('ru', "Гробик кладут в ямку 2 кв. м.", f"Гробик кладут в ямку 2 кв. м."),
|
||||||
('ru', "500 до н. э.", f"500 до н. э."),
|
('ru', "IV-X вв.", f"IV-X{CHAR_NBSP}вв."),
|
||||||
('ru+en', "Хаммурапи (1792 - 1750 до н. э.)", f"Хаммурапи (1792 - 1750 до н. э.)"),
|
('ru', "IV в. н. э.", f"IV{CHAR_NBSP}в. н.{CHAR_THIN_SP}э."),
|
||||||
|
('ru+en', "Хаммурапи (1792 - 1750 до н. э.)",
|
||||||
|
f"Хаммурапи (1792 - 1750 до н.{CHAR_THIN_SP}э.)"),
|
||||||
|
|
||||||
|
# Составные и математические единицы
|
||||||
|
('ru', "Площадь 120 кв. м.", f"Площадь 120{CHAR_NBSP}кв.{CHAR_THIN_SP}м."),
|
||||||
|
('ru', "Площадь 130 кв.м.", f"Площадь 130{CHAR_NBSP}кв.{CHAR_THIN_SP}м."),
|
||||||
|
('ru', f"Площадь 140 {CHAR_NBSP} кв.{CHAR_NBSP}м.", f"Площадь 140{CHAR_NBSP}кв.{CHAR_THIN_SP}м."),
|
||||||
|
('ru', "Площадь 150 тыс. кв. км.", f"Площадь 150{CHAR_NBSP}тыс.{CHAR_THIN_SP}кв.{CHAR_THIN_SP}км."),
|
||||||
|
('ru', "Скорость 90 км/ч", f"Скорость 90{CHAR_NBSP}км/ч"),
|
||||||
|
('ru', "Скорость 90 км / ч", f"Скорость 90{CHAR_NBSP}км/ч"),
|
||||||
|
('ru', "В 500 г. н. э.", f"В 500{CHAR_NBSP}г. н.{CHAR_THIN_SP}э."),
|
||||||
|
|
||||||
|
# Сложные единицы (склеиваются тонкой шпацией, привязываются к числу неразрывным пробелом)
|
||||||
|
('ru', "Дом 120 кв.м. / Участок 6 сот.", f"Дом 120{CHAR_NBSP}кв.{CHAR_THIN_SP}м. / Участок 6{CHAR_NBSP}сот."),
|
||||||
|
# ('ru', "Гробик кладут в ямку 2 кв. м.", f"Гробик кладут в ямку 2 кв. м."),
|
||||||
|
('ru', "500 до н. э.", f"500 до н.{CHAR_THIN_SP}э."),
|
||||||
|
('ru+en', "Хаммурапи (1792 - 1750 до н. э.)", f"Хаммурапи (1792 - 1750 до н.{CHAR_THIN_SP}э.)"),
|
||||||
|
|
||||||
# --- Комбинированные случаи ---
|
# --- Комбинированные случаи ---
|
||||||
('ru', f"Да — это так{CHAR_HELLIP} а может и нет. Счёт -10.",
|
('ru', f"Да — это так{CHAR_HELLIP} а может и нет. Счёт -10.",
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
# tests/test_unbreakables.py
|
# tests/test_unbreakables.py
|
||||||
import pytest
|
import pytest
|
||||||
from etpgrf import Unbreakables
|
from etpgrf import Unbreakables
|
||||||
|
from etpgrf.config import CHAR_NBSP, CHAR_THIN_SP
|
||||||
|
|
||||||
|
|
||||||
# Список русских слов, которые должны "приклеиваться" к следующему слову.
|
# Список русских слов, которые должны "приклеиваться" к следующему слову.
|
||||||
@@ -26,7 +27,7 @@ def test_russian_prepositions_are_unbreakable(word):
|
|||||||
# Arrange (подготовка)
|
# Arrange (подготовка)
|
||||||
unbreakables_ru = Unbreakables(langs='ru')
|
unbreakables_ru = Unbreakables(langs='ru')
|
||||||
input_text = f"Проверка {word} тестирование."
|
input_text = f"Проверка {word} тестирование."
|
||||||
expected_text = f"Проверка {word}\u00A0тестирование."
|
expected_text = f"Проверка {word}{CHAR_NBSP}тестирование."
|
||||||
# Act (действие, которое выполняем)
|
# Act (действие, которое выполняем)
|
||||||
actual_text = unbreakables_ru.process(input_text)
|
actual_text = unbreakables_ru.process(input_text)
|
||||||
# Assert (утверждение, что результат соответствует ожиданиям)
|
# Assert (утверждение, что результат соответствует ожиданиям)
|
||||||
@@ -48,7 +49,7 @@ def test_english_prepositions_are_unbreakable(word):
|
|||||||
# Arrange (подготовка)
|
# Arrange (подготовка)
|
||||||
unbreakables_en = Unbreakables(langs='en')
|
unbreakables_en = Unbreakables(langs='en')
|
||||||
input_text = f"Training {word} test."
|
input_text = f"Training {word} test."
|
||||||
expected_text = f"Training {word}\u00A0test."
|
expected_text = f"Training {word}{CHAR_NBSP}test."
|
||||||
# Act (действие, которое выполняем)
|
# Act (действие, которое выполняем)
|
||||||
actual_text = unbreakables_en.process(input_text)
|
actual_text = unbreakables_en.process(input_text)
|
||||||
# Assert (утверждение, что результат соответствует ожиданиям)
|
# Assert (утверждение, что результат соответствует ожиданиям)
|
||||||
@@ -63,7 +64,7 @@ def test_mix_prepositions_are_unbreakable():
|
|||||||
# Arrange (подготовка)
|
# Arrange (подготовка)
|
||||||
unbreakables_mix = Unbreakables(langs='ru+en')
|
unbreakables_mix = Unbreakables(langs='ru+en')
|
||||||
input_text = f"Для the Guardian он написал блестящую статью."
|
input_text = f"Для the Guardian он написал блестящую статью."
|
||||||
expected_text = f"Для\u00A0the\u00A0Guardian он\u00A0написал блестящую статью."
|
expected_text = f"Для{CHAR_NBSP}the{CHAR_NBSP}Guardian он{CHAR_NBSP}написал блестящую статью."
|
||||||
# Act (действие, которое выполняем)
|
# Act (действие, которое выполняем)
|
||||||
actual_text = unbreakables_mix.process(input_text)
|
actual_text = unbreakables_mix.process(input_text)
|
||||||
# Assert (утверждение, что результат соответствует ожиданиям)
|
# Assert (утверждение, что результат соответствует ожиданиям)
|
||||||
@@ -83,8 +84,22 @@ def test_russian_postpositive_particle(word):
|
|||||||
# Arrange
|
# Arrange
|
||||||
unbreakables_ru = Unbreakables(langs='ru')
|
unbreakables_ru = Unbreakables(langs='ru')
|
||||||
input_text = f"Отчего {word} не поспать?"
|
input_text = f"Отчего {word} не поспать?"
|
||||||
expected_text = f"Отчего\u00A0{word} не\u00A0поспать?"
|
expected_text = f"Отчего{CHAR_NBSP}{word} не{CHAR_NBSP}поспать?"
|
||||||
# Act
|
# Act
|
||||||
actual_text = unbreakables_ru.process(input_text)
|
actual_text = unbreakables_ru.process(input_text)
|
||||||
# Assert
|
# Assert
|
||||||
assert actual_text == expected_text
|
assert actual_text == expected_text
|
||||||
|
|
||||||
|
|
||||||
|
# Тесты для проверки особых случаев в Unbreakables
|
||||||
|
UNBREAKABLES_SPECIAL_TEST_CASES = [
|
||||||
|
('ru', "до н.э.", f"до{CHAR_NBSP}н.э."),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("lang, input_string, expected_output", UNBREAKABLES_SPECIAL_TEST_CASES)
|
||||||
|
def test_layout_processor_with_options(lang, input_string, expected_output):
|
||||||
|
"""Проверяет работу Unbreakables с особыми случаями. """
|
||||||
|
processor = Unbreakables(langs=lang)
|
||||||
|
actual_output = processor.process(input_string)
|
||||||
|
assert actual_output == expected_output
|
||||||
|
Reference in New Issue
Block a user