mod: Санитайзер для очистки от HTML (несколько режимов)

This commit is contained in:
2025-10-28 23:46:38 +03:00
parent 57fb4914d8
commit 48c90409b8
4 changed files with 193 additions and 4 deletions

View File

@@ -11,7 +11,12 @@ etpgrf - библиотека для экранной типографики т
__version__ = "0.1.0"
import etpgrf.defaults
from etpgrf.typograph import Typographer
from etpgrf.hyphenation import Hyphenator
from etpgrf.unbreakables import Unbreakables
import etpgrf.logger
from etpgrf.hyphenation import Hyphenator
from etpgrf.layout import LayoutProcessor
from etpgrf.quotes import QuotesProcessor
from etpgrf.sanitizer import SanitizerProcessor
from etpgrf.symbols import SymbolsProcessor
from etpgrf.typograph import Typographer
from etpgrf.unbreakables import Unbreakables

View File

@@ -15,6 +15,12 @@ LANG_EN = 'en' # Английский
SUPPORTED_LANGS = frozenset([LANG_RU, LANG_RU_OLD, LANG_EN])
DEFAULT_LANGS = (LANG_RU, LANG_EN) # Языки по умолчанию
# Виды санитизации (очистки) входного текста
SANITIZE_ALL_HTML = "html" # Полная очистка от HTML-тегов
SANITIZE_ETPGRF = "etp" # Очистка от "span-оберток" символов висячей пунктуации (если она была расставлена
# при предыдущих проходах типографа)
SANITIZE_NONE = None # Без очистки (режим по умолчанию). False тоже можно использовать.
# === ИСТОЧНИК ПРАВДЫ ===
# --- Базовые алфавиты: Эти константы используются как для правил переноса, так и для правил кодирования ---
@@ -677,4 +683,40 @@ ABBR_COMMON_PREPOSITION = [
]
# === КОНСТАНТЫ ДЛЯ HTML-ТЕГОВ, ВНУТРИ КОТОРЫХ НЕ НАДО ТИПОГРАФИРОВАТЬ ===
PROTECTED_HTML_TAGS = ['style', 'script', 'pre', 'code', 'kbd', 'samp', 'math']
PROTECTED_HTML_TAGS = ['style', 'script', 'pre', 'code', 'kbd', 'samp', 'math']
# === КОНСТАНТЫ ДЛЯ ВИСЯЧЕЙ ТИПОГРАФИКИ ===
# 1. Набор символов, которые могут "висеть" слева
HANGING_PUNCTUATION_LEFT_CHARS = frozenset([
CHAR_RU_QUOT1_OPEN, # «
CHAR_EN_QUOT1_OPEN, # “
'(', '[', '{',
])
# 2. Набор символов, которые могут "висеть" справа
HANGING_PUNCTUATION_RIGHT_CHARS = frozenset([
CHAR_RU_QUOT1_CLOSE, # »
CHAR_EN_QUOT1_CLOSE, # ”
')', ']', '}',
'.', ',', ':',
])
# 3. Словарь, сопоставляющий символ с его CSS-классом
HANGING_PUNCTUATION_CLASSES = {
# Левая пунктуация: все классы начинаются с 'etp-l'
CHAR_RU_QUOT1_OPEN: 'etp-laquo',
CHAR_EN_QUOT1_OPEN: 'etp-ldquo',
'(': 'etp-lpar',
'[': 'etp-lsqb',
'{': 'etp-lcub',
# Правая пунктуация: все классы начинаются с 'etp-r'
CHAR_RU_QUOT1_CLOSE: 'etp-raquo',
CHAR_EN_QUOT1_CLOSE: 'etp-rdquo',
')': 'etp-rpar',
']': 'etp-rsqb',
'}': 'etp-rcub',
'.': 'etp-r-dot',
',': 'etp-r-comma',
':': 'etp-r-colon',
}

62
etpgrf/sanitizer.py Normal file
View File

@@ -0,0 +1,62 @@
# etpgrf/sanitizer.py
# Модуль для очистки и нормализации HTML-кода перед типографикой.
import logging
from bs4 import BeautifulSoup, NavigableString
from .config import (SANITIZE_ALL_HTML, SANITIZE_ETPGRF, SANITIZE_NONE,
HANGING_PUNCTUATION_CLASSES, PROTECTED_HTML_TAGS)
logger = logging.getLogger(__name__)
class SanitizerProcessor:
"""
Выполняет очистку HTML-кода в соответствии с заданным режимом.
"""
def __init__(self, mode: str | bool | None = SANITIZE_NONE):
"""
:param mode: Режим очистки:
- 'etp' (SANITIZE_ETPGRF): удаляет только разметку висячей пунктуации.
- 'html' (SANITIZE_ALL_HTML): удаляет все HTML-теги.
- None или False: ничего не делает.
"""
if mode is False:
mode = SANITIZE_NONE
self.mode = mode
self._etp_classes_to_clean = frozenset(HANGING_PUNCTUATION_CLASSES.values())
logger.debug(f"SanitizerProcessor `__init__`. Mode: {self.mode}")
def process(self, soup: BeautifulSoup) -> BeautifulSoup | str:
"""
Применяет правила очистки к `soup`-объекту.
:param soup: Объект BeautifulSoup для обработки.
:return: Обработанный объект BeautifulSoup или строка (в режиме 'html').
"""
if self.mode == SANITIZE_ETPGRF:
# Находим все span'ы, у которых есть <span> с хотя бы одним из наших классов висячей пунктуации
spans_to_clean = soup.find_all(
name='span',
class_=lambda c: c and any(etp_class in c.split() for etp_class in self._etp_classes_to_clean)
)
# "Агрессивная" очистка: просто "разворачиваем" все найденные теги,
# заменяя их своим содержимым.
for span in spans_to_clean:
span.unwrap()
return soup
elif self.mode == SANITIZE_ALL_HTML:
# Возвращаем только текст, удаляя все теги
# При этом уважаем защищенные теги, не извлекая текст из них.
text_parts = [
str(node) for node in soup.descendants
if isinstance(node, NavigableString) and node.parent.name not in PROTECTED_HTML_TAGS
]
return "".join(text_parts)
# Если режим не задан, ничего не делаем
return soup