add: Препозиционные сокращения ('и.о.', 'т.о.', 'т.к.', 'т.е.' и прочее-прочее)
This commit is contained in:
17
README.md
17
README.md
@@ -240,6 +240,23 @@ typo = etpgrf.Typographer(process_units=False)
|
||||
result = typo.process("100 км/ч") # Останется без изменений
|
||||
```
|
||||
|
||||
#### Сокращения
|
||||
|
||||
Типограф также обрабатывает распространённые русскоязычные сокращения, чтобы они корректно отображались и не разрывались
|
||||
при переносе строк. Правила делятся на два типа:
|
||||
* Финальные сокращения. Сокращения, которые обычно стоят в конце фразы (например, и т. д., и т. п.),
|
||||
обрабатываются особым образом: их части «склеиваются» тонкой шпацией, а перед всей конструкцией ставится неразрывный
|
||||
пробел, чтобы она не «повисла» на новой строке. `...и так далее, и т. д.` → `...и так далее, и т. д.`
|
||||
Это правило работает независимо от того, как сокращение было написано в исходном тексте (т.д. или т. д.).
|
||||
* Препозиционные сокращения. Сокращения, которые стоят перед другим словом (например, и. о. директора, т. е. сказать),
|
||||
также «склеиваются» внутри, но неразрывный пробел ставится после них, чтобы привязать их к последующему слову.
|
||||
`Назначить и. о. директора` → `Назначить и. о. директора`
|
||||
|
||||
Библиотека знает небольшой набор самых распространённых сокращений. Но не все, а некоторые принципиально невозможны
|
||||
к обработке. Например, сокращение `пр.` может оказаться как финальным (в значении «и так далее»), так и препозиционным
|
||||
(в значении «профессор» или «проспект»). Так же типограф не обрабатывает сокращения, связанные с адресами (ул., д.,
|
||||
кв., пл., наб. ...) так как они могут быть как финальными, так и препозиционными.
|
||||
|
||||
|
||||
## P.S.
|
||||
|
||||
|
@@ -659,9 +659,12 @@ UNIT_MATH_OPERATORS = ['/', '*', '×', CHAR_MIDDOT, '÷']
|
||||
# Эти сокращения (обычно в конце фразы) будут "склеены" тонкой шпацией, а перед ними будет поставлен неразрывный пробел.
|
||||
# Важно, чтобы многосложные сокращения (типа "и т. д.") были в списке с разделителем пробелом (иначе мы не сможем их найти).
|
||||
ABBR_COMMON_FINAL = [
|
||||
'т. д.', 'т. п.', 'др.', 'пр.',
|
||||
# 'т. д.', 'т. п.', 'др.', 'пр.',
|
||||
# УБРАНЫ из-за неоднозначности: др. -- "другой", "доктор", "драм" / пр. -- "прочие", "профессор", "проект", "проезд" ...
|
||||
'т. д.', 'т. п.',
|
||||
]
|
||||
|
||||
ABBR_COMMON_PREPOSITION = [
|
||||
'т.е.', 'т.к.', 'т.о.', 'и.о.', 'ио', 'вр.и.о.', 'врио'
|
||||
'т. е.', 'т. к.', 'т. о.', 'и. о.', 'ио', 'вр. и. о.', 'врио', 'тов.', 'г-н.', 'г-жа.', 'им.',
|
||||
'д. о. с.', 'д. о. н.', 'д. м. н.', 'к. т. д.', 'к. т. п.',
|
||||
]
|
@@ -5,7 +5,7 @@ import regex
|
||||
import logging
|
||||
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, UNIT_MATH_OPERATORS,
|
||||
ABBR_COMMON_FINAL)
|
||||
ABBR_COMMON_FINAL, ABBR_COMMON_PREPOSITION)
|
||||
|
||||
from etpgrf.comutil import parse_and_validate_langs
|
||||
|
||||
@@ -124,6 +124,39 @@ class LayoutProcessor:
|
||||
# По умолчанию (и для русского) — отбивка пробелами.
|
||||
return f'{CHAR_NBSP}{dash} '
|
||||
|
||||
def _process_abbreviations(self, text: str, abbreviations: list[str], mode: str) -> str:
|
||||
"""
|
||||
Универсальный обработчик для разных типов сокращений.
|
||||
|
||||
:param text: Входной текст.
|
||||
:param abbreviations: Список сокращений для обработки.
|
||||
:param mode: 'final' (NBSP ставится перед) или 'prepositional' (NBSP ставится после).
|
||||
:return: Обработанный текст.
|
||||
"""
|
||||
processed_text = text
|
||||
|
||||
# Шаг 1: "Склеиваем" многосоставные сокращения временным разделителем CHAR_UNIT_SEPARATOR
|
||||
for abbr in sorted(abbreviations, key=len, reverse=True):
|
||||
if ' ' in abbr:
|
||||
pattern = regex.escape(abbr).replace(r'\ ', r'\s*')
|
||||
replacement = abbr.replace(' ', CHAR_UNIT_SEPARATOR)
|
||||
processed_text = regex.sub(pattern, replacement, processed_text, flags=regex.IGNORECASE)
|
||||
|
||||
# Шаг 2: Ставим неразрывный пробел.
|
||||
glued_abbrs = [a.replace(' ', CHAR_UNIT_SEPARATOR) for a in abbreviations]
|
||||
all_abbrs_pattern = '|'.join(map(regex.escape, sorted(glued_abbrs, key=len, reverse=True)))
|
||||
|
||||
if mode == 'final':
|
||||
# Ставим nbsp перед сокращением, если перед ним есть пробел
|
||||
nbsp_pattern = regex.compile(r'(\s)(' + all_abbrs_pattern + r')(?=[.,!?]|\s|$)', flags=regex.IGNORECASE)
|
||||
processed_text = nbsp_pattern.sub(fr'{CHAR_NBSP}\2', processed_text)
|
||||
elif mode == 'prepositional':
|
||||
# Ставим nbsp после сокращения, если после него есть пробел
|
||||
nbsp_pattern = regex.compile(r'(' + all_abbrs_pattern + r')(\s)', flags=regex.IGNORECASE)
|
||||
processed_text = nbsp_pattern.sub(fr'\1{CHAR_NBSP}', processed_text)
|
||||
|
||||
# Шаг 3: Заменяем временный разделитель на правильную тонкую шпацию
|
||||
return processed_text.replace(CHAR_UNIT_SEPARATOR, CHAR_THIN_SP)
|
||||
|
||||
def process(self, text: str) -> str:
|
||||
"""Применяет правила компоновки к тексту."""
|
||||
@@ -139,24 +172,9 @@ class LayoutProcessor:
|
||||
# 3. Обработка пробела перед отрицательными числами/минусом.
|
||||
processed_text = self._negative_number_pattern.sub(f'{CHAR_NBSP}-\\1', processed_text)
|
||||
|
||||
# 4. Обработка финальных сокращений (т.д., т.п. и т.д.)
|
||||
# Шаг 1: "Склеиваем" многосоставные сокращения временным разделителем.
|
||||
temp_processed_text = processed_text
|
||||
for abbr in ABBR_COMMON_FINAL:
|
||||
if ' ' in abbr: # Обрабатываем только многосоставные
|
||||
pattern = regex.escape(abbr).replace(r'\ ', r'\s*')
|
||||
replacement = abbr.replace(' ', CHAR_UNIT_SEPARATOR)
|
||||
temp_processed_text = regex.sub(pattern, replacement, temp_processed_text, flags=regex.IGNORECASE)
|
||||
|
||||
# Шаг 2: Ставим неразрывный пробел перед всеми финальными сокращениями (уже "склеенными").
|
||||
# Создаем паттерн из всех вариантов - и простых, и "склеенных".
|
||||
glued_abbrs = [a.replace(' ', CHAR_UNIT_SEPARATOR) for a in ABBR_COMMON_FINAL]
|
||||
all_final_abbrs_pattern = '|'.join(map(regex.escape, sorted(glued_abbrs, key=len, reverse=True)))
|
||||
nbsp_pattern = regex.compile(r'(\s)(' + all_final_abbrs_pattern + r')(?=[.,!?]|\s|$)', flags=regex.IGNORECASE)
|
||||
processed_text = nbsp_pattern.sub(fr'{CHAR_NBSP}\2', temp_processed_text)
|
||||
|
||||
# Шаг 3: Заменяем временный разделитель на правильную тонкую шпацию.
|
||||
processed_text = processed_text.replace(CHAR_UNIT_SEPARATOR, CHAR_THIN_SP)
|
||||
# 4. Обработка сокращений.
|
||||
processed_text = self._process_abbreviations(processed_text, ABBR_COMMON_FINAL, 'final')
|
||||
processed_text = self._process_abbreviations(processed_text, ABBR_COMMON_PREPOSITION, 'prepositional')
|
||||
|
||||
# 5. Обработка инициалов и акронимов (если включено).
|
||||
if self.process_initials_and_acronyms:
|
||||
|
@@ -127,20 +127,36 @@ LAYOUT_TEST_CASES = [
|
||||
('ru', "За окном 15 °C", f"За окном 15{CHAR_NBSP}°C"),
|
||||
('ru', "HiFi 20 Гц - 20 кГц", f"HiFi 20{CHAR_NBSP}Гц - 20{CHAR_NBSP}кГц"),
|
||||
|
||||
# Финальные сокращения
|
||||
('ru', "1 и т.д.", f"1 и{CHAR_NBSP}т.{CHAR_THIN_SP}д."),
|
||||
('ru', "2 и т. д.", f"2 и{CHAR_NBSP}т.{CHAR_THIN_SP}д."),
|
||||
('ru', "3 и т.д., и др.", f"3 и{CHAR_NBSP}т.{CHAR_THIN_SP}д., и{CHAR_NBSP}др."), # Слитное написание
|
||||
('ru', "4 и т.п., и пр.", f"4 и{CHAR_NBSP}т.{CHAR_THIN_SP}п., и{CHAR_NBSP}пр."), # Слитное написание
|
||||
('ru', "5 и т. п., и т.п., и пр.", f"5 и{CHAR_NBSP}т.{CHAR_THIN_SP}п., и{CHAR_NBSP}т.{CHAR_THIN_SP}п., и{CHAR_NBSP}пр."), # Слитное и раздельное написание
|
||||
|
||||
|
||||
# Сложные единицы (склеиваются тонкой шпацией, привязываются к числу неразрывным пробелом)
|
||||
('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', "1 и т.д.", f"1 и{CHAR_NBSP}т.{CHAR_THIN_SP}д."),
|
||||
('ru', "2 и т. \n д.", f"2 и{CHAR_NBSP}т.{CHAR_THIN_SP}д."),
|
||||
('ru', "3 и т.д., и др.", f"3 и{CHAR_NBSP}т.{CHAR_THIN_SP}д., и др."), # Слитное написание
|
||||
('ru', "4 и т.п., и пр.", f"4 и{CHAR_NBSP}т.{CHAR_THIN_SP}п., и пр."), # Слитное написание
|
||||
('ru', "5 и т. п., и т.п., и пр.", f"5 и{CHAR_NBSP}т.{CHAR_THIN_SP}п., и{CHAR_NBSP}т.{CHAR_THIN_SP}п., и пр."), # Слитное и раздельное написание
|
||||
|
||||
# Препозиционные сокращения
|
||||
('ru', "Назначить и.о. директора", f"Назначить и.{CHAR_THIN_SP}о.{CHAR_NBSP}директора"),
|
||||
('ru', "Назначить ио директора", f"Назначить ио{CHAR_NBSP}директора"), # без точек
|
||||
('ru', "замечаний не было, т. е. не было и ошибок", f"замечаний не было, т.{CHAR_THIN_SP}е.{CHAR_NBSP}не было и ошибок"),
|
||||
('ru', "Назначить и. о. директора", f"Назначить и.{CHAR_THIN_SP}о.{CHAR_NBSP}директора"), # с пробелом
|
||||
('ru', "Назначить и.о. директора", f"Назначить и.{CHAR_THIN_SP}о.{CHAR_NBSP}директора"), # без пробела
|
||||
('ru', "Назначить ио директора", f"Назначить ио{CHAR_NBSP}директора"), # без точек
|
||||
('ru', "то есть т. е. сказать", f"то есть т.{CHAR_THIN_SP}е.{CHAR_NBSP}сказать"),
|
||||
('ru', "таким образом, т. о. мы видим", f"таким образом, т.{CHAR_THIN_SP}о.{CHAR_NBSP}мы видим"),
|
||||
('ru', "потому что т.к. это важно", f"потому что т.{CHAR_THIN_SP}к.{CHAR_NBSP}это важно"),
|
||||
('ru', "Назначить вр. и. о. начальника", f"Назначить вр.{CHAR_THIN_SP}и.{CHAR_THIN_SP}о.{CHAR_NBSP}начальника"),
|
||||
('ru', "Назначить врио начальника", f"Назначить врио{CHAR_NBSP}начальника"),
|
||||
('ru', "Выступает тов. Сухов", f"Выступает тов.{CHAR_NBSP}Сухов"),
|
||||
('ru', "Приехал г-н. Петров", f"Приехал г-н.{CHAR_NBSP}Петров"),
|
||||
('ru', "Институт им. Курчатова", f"Институт им.{CHAR_NBSP}Курчатова"),
|
||||
('ru', "собаку оперировал д. м. н. профессор Преображенский", f"собаку оперировал д.{CHAR_THIN_SP}м.{CHAR_THIN_SP}н.{CHAR_NBSP}профессор Преображенский"),
|
||||
|
||||
# --- Комбинированные случаи ---
|
||||
('ru', f"Да — это так{CHAR_HELLIP} а может и нет. Счёт -10.",
|
||||
f"Да{CHAR_NBSP}— это так{CHAR_HELLIP}{CHAR_NBSP}а может и нет. Счёт{CHAR_NBSP}-10."),
|
||||
|
Reference in New Issue
Block a user