add: обработка html (теги исклчены из типографа).
This commit is contained in:
@@ -30,7 +30,7 @@ SHY_ENTITIES = {
|
||||
SPACE_ENTITIES = {
|
||||
'NBSP': ('\u00A0', ' '), # Неразрывный пробел
|
||||
'THINSP': ('\u2009', ' '), # Тонкий пробел
|
||||
'ENSP': ('\u2002', ' '), # Полуширокий пробел
|
||||
'ENSP': ('\u2002', ' '), # Полу-широкий пробел
|
||||
'EMSP': ('\u2003', ' '), # Широкий пробел
|
||||
'ZWNJ': ('\u200C', '‌'), # Разрывный пробел нулевой ширины (без пробела)
|
||||
'ZWJ': ('\u200D', '‍'), # Неразрывный пробел нулевой ширины
|
||||
@@ -38,9 +38,10 @@ SPACE_ENTITIES = {
|
||||
|
||||
# Тире и дефисы
|
||||
DASH_ENTITIES = {
|
||||
'NDASH': ('\u2013', '–'), # Короткое тире
|
||||
'NDASH': ('\u2013', '–'), # Cреднее тире (En dash)
|
||||
'MDASH': ('\u2014', '—'), # Длинное тире
|
||||
# 'HYPHEN': ('\u2010', '‐'), # Обычный дефис (если нужно отличать от минуса)
|
||||
'HYPHEN': ('\u2010', '‐'), # Обычный дефис (если нужно отличать от минуса)
|
||||
'HORBAR': ('\u2015', '―'), # Горизонтальная линия (длинная черта)
|
||||
}
|
||||
|
||||
# Кавычки
|
||||
@@ -57,7 +58,16 @@ QUOTE_ENTITIES = {
|
||||
'SBQUO': ('\u201A', '‚'), # Нижняя одинарная кавычка -- ‚
|
||||
'LSAQUO': ('\u2039', '‹'), # Открывающая французская угловая кавычка -- ›
|
||||
'RSAQUO': ('\u203A', '›'), # Закрывающая французская угловая кавычка -- ‹
|
||||
}
|
||||
|
||||
CURRENCY_ENTITIES = {
|
||||
'DOLLAR': ('\u0024', '$'), # Доллар
|
||||
'CENT': ('\u00A2', '¢'), # Цент
|
||||
'POUND': ('\u00A3', '£'), # Фунт стерлингов
|
||||
'CURREN': ('\u00A4', '¤'), # Знак валюты (обычно используется для обозначения "без конкретной валюты")
|
||||
'YEN': ('\u00A5', '¥'), # Йена
|
||||
'EURO': ('\u20AC', '€'), # Евро
|
||||
'RUBLE': ('\u20BD', '₽'), # Российский рубль (₽)
|
||||
}
|
||||
|
||||
# Другие символы (пример для расширения)
|
||||
|
@@ -1,7 +1,12 @@
|
||||
import logging
|
||||
try:
|
||||
from bs4 import BeautifulSoup, NavigableString
|
||||
except ImportError:
|
||||
BeautifulSoup = None
|
||||
from etpgrf.comutil import parse_and_validate_mode, parse_and_validate_langs
|
||||
from etpgrf.hyphenation import Hyphenator
|
||||
from etpgrf.unbreakables import Unbreakables
|
||||
import logging
|
||||
|
||||
|
||||
# --- Настройки логирования ---
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -12,6 +17,7 @@ class Typographer:
|
||||
def __init__(self,
|
||||
langs: str | list[str] | tuple[str, ...] | frozenset[str] | None = None,
|
||||
mode: str | None = None,
|
||||
process_html: bool = False, # Флаг обработки HTML-тегов
|
||||
hyphenation: Hyphenator | bool | None = True, # Перенос слов и параметры расстановки переносов
|
||||
unbreakables: Unbreakables | bool | None = True, # Правила для предотвращения разрыва коротких слов
|
||||
# ... другие модули правил ...
|
||||
@@ -21,7 +27,14 @@ class Typographer:
|
||||
self.langs: frozenset[str] = parse_and_validate_langs(langs)
|
||||
# B. --- Обработка и валидация параметра mode ---
|
||||
self.mode: str = parse_and_validate_mode(mode)
|
||||
# C. --- Инициализация правила переноса ---
|
||||
# C. --- Настройка режима обработки HTML ---
|
||||
self.process_html = process_html
|
||||
if self.process_html and BeautifulSoup is None:
|
||||
logger.warning("Параметр 'process_html=True', но библиотека BeautifulSoup не установлена. "
|
||||
"HTML не будет обработан. Установите ее: `pip install beautifulsoup4`")
|
||||
self.process_html = False
|
||||
|
||||
# D. --- Инициализация правила переноса ---
|
||||
# Предпосылка: если вызвали типограф, значит, мы хотим обрабатывать текст и переносы тоже нужно расставлять.
|
||||
# А для специальных случаев, когда переносы не нужны, пусть не ленятся и делают `hyphenation=False`.
|
||||
self.hyphenation: Hyphenator | None = None
|
||||
@@ -31,13 +44,8 @@ class Typographer:
|
||||
elif isinstance(hyphenation, Hyphenator):
|
||||
# C2. Если hyphenation - это объект Hyphenator, то просто сохраняем его (и используем его langs и mode)
|
||||
self.hyphenation = hyphenation
|
||||
elif hyphenation is False:
|
||||
# C3. Если hyphenation - False, то правило переноса выключено.
|
||||
self.hyphenation = None
|
||||
else:
|
||||
# D4. Если hyphenation что-то неведомое, то игнорируем его и правило переноса выключено
|
||||
self.hyphenation = None
|
||||
# D. --- Конфигурация правил неразрывных слов ---
|
||||
|
||||
# E. --- Конфигурация правил неразрывных слов ---
|
||||
self.unbreakables: Unbreakables | None = None
|
||||
if unbreakables is True or unbreakables is None:
|
||||
# D1. Создаем новый объект Unbreakables с заданными языками и режимом, а все остальное по умолчанию
|
||||
@@ -45,37 +53,75 @@ class Typographer:
|
||||
elif isinstance(unbreakables, Unbreakables):
|
||||
# D2. Если unbreakables - это объект Unbreakables, то просто сохраняем его (и используем его langs и mode)
|
||||
self.unbreakables = unbreakables
|
||||
elif unbreakables is False:
|
||||
# D3. Если unbreakables - False, то правило неразрывных слов выключено.
|
||||
self.unbreakables = None
|
||||
else:
|
||||
# D4. Если unbreakables что-то неведомое, то игнорируем его и правило неразрывных слов выключено
|
||||
self.unbreakables = None
|
||||
# E. --- Конфигурация других правил---
|
||||
|
||||
# F. --- Конфигурация других правил---
|
||||
|
||||
# Z. --- Логирование инициализации ---
|
||||
logger.debug(f"Typographer `__init__`: langs: {self.langs}, mode: {self.mode}, "
|
||||
f"hyphenation: {self.hyphenation is not None}, "
|
||||
f"unbreakables: {self.unbreakables is not None}")
|
||||
f"unbreakables: {self.unbreakables is not None}"
|
||||
f"process_html: {self.process_html}")
|
||||
|
||||
|
||||
def _process_text_node(self, text: str) -> str:
|
||||
"""
|
||||
Внутренний конвейер, который работает с чистым текстом.
|
||||
"""
|
||||
# Шаг 1: Декодируем весь входящий текст в канонический Unicode
|
||||
# (здесь можно использовать html.unescape, но наш кодек тоже подойдет)
|
||||
# processed_text = decode_to_unicode(text)
|
||||
processed_text = text # ВРЕМЕННО: используем текст как есть
|
||||
|
||||
# Шаг 2: Применяем правила к чистому Unicode-тексту
|
||||
if self.unbreakables is not None:
|
||||
processed_text = self.unbreakables.process(processed_text)
|
||||
if self.hyphenation is not None:
|
||||
processed_text = self.hyphenation.hyp_in_text(processed_text)
|
||||
# ... вызовы других активных модулей правил ...
|
||||
|
||||
# Шаг 3: Кодируем результат в запрошенный формат (mnemonic или mixed)
|
||||
# final_text = encode_from_unicode(processed_text, self.mode)
|
||||
final_text = processed_text # ВРЕМЕННО: используем текст как есть
|
||||
return final_text
|
||||
|
||||
|
||||
|
||||
|
||||
# Конвейер для обработки текста
|
||||
def process(self, text: str) -> str:
|
||||
processed_text = text
|
||||
# Применяем правила в определенном порядке.
|
||||
# Неразрывные конструкции лучше применять до переносов.
|
||||
if self.unbreakables is not None:
|
||||
processed_text = self.unbreakables.process(processed_text)
|
||||
if self.hyphenation is not None:
|
||||
# Обработчик переносов (Hyphenator) активен. Обрабатываем текст...
|
||||
processed_text = self.hyphenation.hyp_in_text(processed_text)
|
||||
"""
|
||||
Обрабатывает текст, применяя все активные правила типографики.
|
||||
Поддерживает обработку текста внутри HTML-тегов.
|
||||
"""
|
||||
if not text:
|
||||
return ""
|
||||
# Если включена обработка HTML и BeautifulSoup доступен
|
||||
if self.process_html:
|
||||
# Мы передаем 'html.parser', он быстрый и встроенный.
|
||||
soup = BeautifulSoup(markup=text, features='html.parser')
|
||||
text_nodes = soup.find_all(string=True)
|
||||
for node in text_nodes:
|
||||
# Пропускаем пустые или состоящие из пробелов узлы и узлы внутри тегов, где не нужно обрабатывать текст
|
||||
if not node.string.strip() or node.parent.name in ['style', 'script', 'pre', 'code']:
|
||||
continue
|
||||
# К каждому текстовому узлу применяем "внутренний" процессор
|
||||
processed_node_text = self._process_text_node(node.string)
|
||||
# Отладочная печать, чтобы видеть, что происходит
|
||||
if node.string != processed_node_text:
|
||||
logger.info(f"Processing node: '{node.string}' -> '{processed_node_text}'")
|
||||
# Заменяем узел в дереве на обработанный текст.
|
||||
# BeautifulSoup сама позаботится об экранировании, если нужно.
|
||||
# Важно: мы не можем просто заменить строку, нужно создать новый объект NavigableString,
|
||||
# чтобы BeautifulSoup правильно обработал символы вроде '<' и '>'.
|
||||
# Однако, replace_with достаточно умен, чтобы справиться с этим.
|
||||
node.replace_with(processed_node_text)
|
||||
|
||||
# if self.glue_prepositions_rule:
|
||||
# processed_text = self.glue_prepositions_rule.hyp_in_text(processed_text, non_breaking_space_char=self._get_nbsp())
|
||||
|
||||
# ... вызовы других активных модулей правил ...
|
||||
return processed_text
|
||||
# Возвращаем измененный HTML. BeautifulSoup по умолчанию выводит без тегов <html><body>
|
||||
# если их не было в исходной строке.
|
||||
return str(soup)
|
||||
else:
|
||||
# Если HTML-режим выключен, работаем как раньше
|
||||
return self._process_text_node(text)
|
||||
|
||||
# def _get_nbsp(self): # Пример получения неразрывного пробела
|
||||
# return "\u00A0" if self.mode in UTF else " "
|
||||
|
32
main.py
32
main.py
@@ -60,8 +60,6 @@ if __name__ == '__main__':
|
||||
print(result, "\n-----\n\n-----")
|
||||
|
||||
# Проверяем переносы в смешанном тексте (русский + английский)
|
||||
etpgrf.defaults.etpgrf_settings.hyphenation.MAX_UNHYPHENATED_LEN = 6
|
||||
typo_en = etpgrf.Typographer(langs='en', mode='mixed', hyphenation=True)
|
||||
txt = ("It was a chilly autumn afternoon when Anna finally received her custom-made KATEBLASH coat."
|
||||
" “I can’t believe how perfectly it fits!” she exclaimed, wrapping the soft, woolen fabric tightly"
|
||||
" around her shoulders.\n"
|
||||
@@ -81,25 +79,19 @@ if __name__ == '__main__':
|
||||
"\n"
|
||||
"Later, over coffee, Anna joked, “I told the tailor, ‘Make it so I never want to take it off.’ "
|
||||
"Looks like they succeeded!")
|
||||
etpgrf.defaults.etpgrf_settings.hyphenation.MAX_UNHYPHENATED_LEN = 6
|
||||
typo_en = etpgrf.Typographer(langs='en', mode='mixed', hyphenation=True)
|
||||
result = typo_en.process(text=txt)
|
||||
print(result, "\n\n")
|
||||
print(result, "\n\n--------------\n\n")
|
||||
|
||||
# Проверяем если есть HTML-тегов
|
||||
txt = ("<p>As they walked down the street, Anna noticed how the coat’s tailored cut moved gracefully with her."
|
||||
" The consideration of every detail - from the <i>choice of fabric</i> to the delicate embroidery - made it"
|
||||
" clear that this was no ordinary coat.</p><style>body { font-family: Arial; }</style>")
|
||||
typo_en = etpgrf.Typographer(langs='en', mode='mixed', process_html=True, hyphenation=True)
|
||||
result = typo_en.process(text=txt)
|
||||
print(result, "\n\n--------------\n\n")
|
||||
|
||||
|
||||
# Спасибо. Для английского текста, для проверки типографа, мне не хватает неразрывных диграфов-квадрографов -- sh, ch, th, ph, wh, ck, ng, aw, tch, dge, igh, eigh, ough и неразрывных суффиксов -- ation, ition, ution, osity, able, ible, ment, ness, less, ship, hood, tive, sion, tion в длинный словах (8 символов и более). и пусть тескт тоже будет про пальто KATEBLASH. Справишься??
|
||||
|
||||
# меняем настройки логирования
|
||||
etpgrf.defaults.etpgrf_settings.logging_settings.LEVEL = logging.DEBUG
|
||||
etpgrf.logger.update_etpgrf_log_level_from_settings() # Обновляем уровень логирования из настроек
|
||||
# etpgrf.defaults.etpgrf_settings.logging_settings.FORMAT = '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
||||
# etpgrf.logger.update_etpgrf_log_format_from_settings() # Обновляем формат логирования из настроек
|
||||
# Определяем пользовательские правила переносов
|
||||
hyphen_settings = etpgrf.Hyphenator(langs='en', max_unhyphenated_len=6)
|
||||
|
||||
# Проверяем переносы в словах
|
||||
result = hyphen_settings.hyp_in_text("oughtstanding")
|
||||
print(result, "==\n\n")
|
||||
result = hyphen_settings.hyp_in_text("blacksmithing")
|
||||
print(result, "==\n\n")
|
||||
result = hyphen_settings.hyp_in_text("dccadckpoooughremawgreen")
|
||||
print(result, "==\n\n")
|
||||
|
||||
|
||||
|
6
requirement.txt
Normal file
6
requirement.txt
Normal file
@@ -0,0 +1,6 @@
|
||||
regex==2024.11.6
|
||||
|
||||
beautifulsoup4==4.13.4
|
||||
soupsieve==2.7
|
||||
typing_extensions==4.14.1
|
||||
|
Reference in New Issue
Block a user