diff --git a/etpgrf/config.py b/etpgrf/config.py index 7b39526..7baecdc 100644 --- a/etpgrf/config.py +++ b/etpgrf/config.py @@ -37,6 +37,7 @@ EN_ALPHABET_FULL = EN_ALPHABET_UPPER | EN_ALPHABET_LOWER # --- Специальные символы --- CHAR_NBSP = '\u00a0' # Неразрывный пробел ( ) CHAR_SHY = '\u00ad' # Мягкий перенос (­) +CHAR_THIN_SP = '\u2009' # Тонкий пробел (шпация,  ) CHAR_NDASH = '\u2013' # Cреднее тире (– / –) CHAR_MDASH = '\u2014' # Длинное тире (— / —) CHAR_HELLIP = '\u2026' # Многоточие (… / …) @@ -108,7 +109,7 @@ SAFE_MODE_CHARS_TO_MNEMONIC = frozenset([ '\u2003', # Широкий пробел (Em Space) --   '\u2007', # Цифровой пробел --   '\u2008', # Пунктуационный пробел --   - '\u2009', # Межсимвольный пробел --  ' + CHAR_THIN_SP, # Межсимвольный пробел, тонкий пробел, шпация --  ' '\u200A', # Толщина волоса (Hair Space) --   '\u200B', # Негативный пробел (Negative Space) -- ​ '\u200C', # Нулевая ширина (без объединения) (Zero Width Non-Joiner) -- ‍ @@ -546,7 +547,7 @@ CUSTOM_ENCODE_MAP = { '\u231d': '⌝', # ⌝ / ⌝ / ⌝ '\u2016': '‖', # ‖ / ‖ / ‖ '\u2228': '∨', # ∨ / ∨ / ∨ - '\u2009': ' ', # /   /   + CHAR_THIN_SP: ' ', # /   /   '\u2240': '≀', # ≀ / ≀ / ≀ / ≀ '\u2128': 'ℨ', # ℨ / ℨ / ℨ '\u2118': '℘', # ℘ / ℘ / ℘ diff --git a/etpgrf/layout.py b/etpgrf/layout.py index 9962e29..71daa6a 100644 --- a/etpgrf/layout.py +++ b/etpgrf/layout.py @@ -3,7 +3,7 @@ import regex import logging -from etpgrf.config import LANG_RU, LANG_EN, CHAR_NBSP, 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 from etpgrf.comutil import parse_and_validate_langs # -- @@ -23,10 +23,10 @@ class LayoutProcessor: def __init__(self, langs: str | list[str] | tuple[str, ...] | frozenset[str] | None = None, - process_initials: bool = True): + process_initials_and_acronyms: bool = True): self.langs = parse_and_validate_langs(langs) self.main_lang = self.langs[0] if self.langs else LANG_RU - self.process_initials = process_initials + self.process_initials_and_acronyms = process_initials_and_acronyms # 1. Паттерн для длинного (—) или среднего (–) тире, окруженного пробелами. # (?<=\S) и (?=\S) гарантируют, что тире находится между словами, а не в начале/конце строки. @@ -46,18 +46,23 @@ class LayoutProcessor: # в выражениях типа "10 - 5". self._negative_number_pattern = regex.compile(r'(? str: @@ -85,10 +90,14 @@ class LayoutProcessor: processed_text = self._negative_number_pattern.sub(f'{CHAR_NBSP}-\\1', processed_text) # 4. Обработка инициалов (если включено). - if self.process_initials: - # Сначала связываем фамилию с первым инициалом (Пушкин А. -> Пушкин{NBSP}А.) - processed_text = self._surname_initial_pattern.sub(f'\\1{CHAR_NBSP}', processed_text) - # Затем связываем инициалы между собой и с фамилией (А. С. Пушкин -> А.{NBSP}С.{NBSP}Пушкин) - processed_text = self._initial_pattern.sub(f'\\1{CHAR_NBSP}', processed_text) + if self.process_initials_and_acronyms: + # Сначала вставляем тонкие пробелы там, где пробелов не было. + processed_text = self._initial_to_initial_ns_pattern.sub(f'\\1{CHAR_THIN_SP}', processed_text) + processed_text = self._initial_to_surname_ns_pattern.sub(f'\\1{CHAR_THIN_SP}', processed_text) + + # Затем заменяем существующие пробелы на неразрывные. + processed_text = self._initial_to_initial_ws_pattern.sub(f'\\1{CHAR_NBSP}', processed_text) + processed_text = self._initial_to_surname_ws_pattern.sub(f'\\1{CHAR_NBSP}', processed_text) + processed_text = self._surname_to_initial_ws_pattern.sub(f'\\1{CHAR_NBSP}', processed_text) return processed_text diff --git a/tests/test_layout.py b/tests/test_layout.py index d2030a9..2b59e84 100644 --- a/tests/test_layout.py +++ b/tests/test_layout.py @@ -2,8 +2,8 @@ # Тестирует модуль LayoutProcessor. Проверяет обработку тире и специальных символов в тексте. import pytest -from etpgrf.layout import LayoutProcessor -from etpgrf.config import CHAR_NBSP, CHAR_HELLIP +from etpgrf.layout import LayoutProcessor, CHAR_THIN_SP +from etpgrf.config import CHAR_NBSP, CHAR_HELLIP, CHAR_THIN_SP LAYOUT_TEST_CASES = [ # --- Длинное тире (—) для русского языка --- @@ -71,25 +71,38 @@ LAYOUT_TEST_CASES = [ ('ru', "1-2-3-4-5, я иду тебя искать", "1-2-3-4-5, я иду тебя искать"), # Дефис, а не минус # --- Инициалы (должны обрабатываться по умолчанию) --- + # Разные комбинации пробелов ('ru', "А. С. Пушкин", f"А.{CHAR_NBSP}С.{CHAR_NBSP}Пушкин"), - ('ru', "А.С. Пушкин", f"А.С.{CHAR_NBSP}Пушкин"), - ('ru', "Пушкин А. С.", f"Пушкин{CHAR_NBSP}А.{CHAR_NBSP}С."), - ('ru', "Пушкин А.С.", f"Пушкин{CHAR_NBSP}А.С."), + ('ru', "А.С. Пушкин", f"А.{CHAR_THIN_SP}С.{CHAR_NBSP}Пушкин"), + ('ru', "А.С.Пушкин", f"А.{CHAR_THIN_SP}С.{CHAR_THIN_SP}Пушкин"), ('en', "J. R. R. Tolkien", f"J.{CHAR_NBSP}R.{CHAR_NBSP}R.{CHAR_NBSP}Tolkien"), - ('en', "J.R.R. Tolkien", f"J.R.R.{CHAR_NBSP}Tolkien"), + ('en', "J.R.R. Tolkien", f"J.{CHAR_THIN_SP}R.{CHAR_THIN_SP}R.{CHAR_NBSP}Tolkien"), + ('ru', "Пушкин А. С.", f"Пушкин{CHAR_NBSP}А.{CHAR_NBSP}С."), + ('ru', "Пушкин А.С.", f"Пушкин{CHAR_NBSP}А.{CHAR_THIN_SP}С."), ('en', "Tolkien J. R. R.", f"Tolkien{CHAR_NBSP}J.{CHAR_NBSP}R.{CHAR_NBSP}R."), - ('en', "Tolkien J.R.R.", f"Tolkien{CHAR_NBSP}J.R.R."), + ('en', "Tolkien J.R.R.", f"Tolkien{CHAR_NBSP}J.{CHAR_THIN_SP}R.{CHAR_THIN_SP}R."), + # Один инициал ('ru', "Это был В. Высоцкий.", f"Это был В.{CHAR_NBSP}Высоцкий."), - ('ru', "Высоцкий В. С. был гением.", f"Высоцкий{CHAR_NBSP}В.{CHAR_NBSP}С. был гением."), + ('ru', "Высоцкий В. был гением.", f"Высоцкий{CHAR_NBSP}В. был гением."), + # Акронимы (бонус) + ('ru', "Сделано в С.Ш.А.", f"Сделано в С.{CHAR_THIN_SP}Ш.{CHAR_THIN_SP}А."), + ('ru', "Сделано в С. Ш. А.", f"Сделано в С.{CHAR_NBSP}Ш.{CHAR_NBSP}А."), + ('en', "На замке стояло клеймо «Made in U.S.A.»", f"На замке стояло клеймо «Made in U.{CHAR_THIN_SP}S.{CHAR_THIN_SP}A.»"), + ('en', "На замке стояло клеймо «Made in U. S. A.»", f"На замке стояло клеймо «Made in U.{CHAR_NBSP}S.{CHAR_NBSP}A.»"), + # Никаких изменений, если пробелы другого типа + ('ru', "А.\u200DС.\u200AПушкин", "А.\u200DС.\u200AПушкин"), + ('ru', "Пушкин А.\u200AС.", f"Пушкин{CHAR_NBSP}А.\u200AС."), + ('en', "J.\u200DR.\u200DR.\u200ATolkien", "J.\u200DR.\u200DR.\u200ATolkien"), + ('en', "Tolkien J.\u200AR.\u200AR.", f"Tolkien{CHAR_NBSP}J.\u200AR.\u200AR."), # --- Инициалы (проверка отключения опции) --- # ('ru', "А. С. Пушкин", "А. С. Пушкин", False), - # ('ru', "Пушкин А. С.", "Пушкин А. С.", False), + # ('ru', "Пушкин А.С.", "Пушкин А.С.", False), # --- Комбинированные случаи --- ('ru', f"Да — это так{CHAR_HELLIP} а может и нет. Счёт -10.", f"Да{CHAR_NBSP}— это так{CHAR_HELLIP}{CHAR_NBSP}а может и нет. Счёт{CHAR_NBSP}-10."), - ('ru', f"По мнению А. С. Пушкина — это...", f"По мнению А.{CHAR_NBSP}С.{CHAR_NBSP}Пушкина{CHAR_NBSP}— это..."), + ('ru', f"По мнению А.С.Пушкина — это...", f"По мнению А.{CHAR_THIN_SP}С.{CHAR_THIN_SP}Пушкина{CHAR_NBSP}— это..."), ]