mod: Отключена опция 'both' для висячей пунктуации
This commit is contained in:
@@ -356,7 +356,7 @@ typo = etpgrf.Typographer(hanging_punctuation='left')
|
||||
|
||||
### CSS для висячих символов
|
||||
|
||||
Предлагаемый CSS теперь работает только с `margin` и `padding`, без `position:absolute`. Пробелы получают собственные
|
||||
Предлагаемый, начиная с etpgrf v0.1.6, CSS теперь работает только с `margin` и `padding`, без `position:absolute`. Пробелы получают собственные
|
||||
классы, поэтому их компенсация контролируется отдельно, а не встроена в сам висячий символ. Убедитесь, что эти стили
|
||||
подключены к странице и не конфликтуют с `text-justify`, который вытягивает пробелы по всей строке и разрушает аккуратное
|
||||
выравнивание.
|
||||
|
||||
@@ -690,6 +690,13 @@ PROTECTED_HTML_TAGS = ['style', 'script', 'pre', 'code', 'kbd', 'samp', 'math']
|
||||
|
||||
# === КОНСТАНТЫ ДЛЯ ВИСЯЧЕЙ ТИПОГРАФИКИ ===
|
||||
|
||||
HANGING_PUNCTUATION_MODE_LEFT = 'left'
|
||||
HANGING_PUNCTUATION_MODE_RIGHT = 'right'
|
||||
HANGING_PUNCTUATION_MODES = frozenset([
|
||||
HANGING_PUNCTUATION_MODE_LEFT,
|
||||
HANGING_PUNCTUATION_MODE_RIGHT,
|
||||
])
|
||||
|
||||
# 1. Набор символов, которые могут "висеть" слева
|
||||
HANGING_PUNCTUATION_LEFT_CHARS = frozenset([
|
||||
CHAR_RU_QUOT1_OPEN, # «
|
||||
@@ -723,3 +730,4 @@ HANGING_PUNCTUATION_CLASSES = {
|
||||
',': 'etp-r-comma',
|
||||
':': 'etp-r-colon',
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,9 @@ from bs4 import BeautifulSoup, NavigableString, Tag
|
||||
from .config import (
|
||||
HANGING_PUNCTUATION_LEFT_CHARS,
|
||||
HANGING_PUNCTUATION_RIGHT_CHARS,
|
||||
HANGING_PUNCTUATION_CLASSES
|
||||
HANGING_PUNCTUATION_CLASSES,
|
||||
HANGING_PUNCTUATION_MODE_LEFT,
|
||||
HANGING_PUNCTUATION_MODE_RIGHT,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -21,28 +23,25 @@ class HangingPunctuationProcessor:
|
||||
"""
|
||||
:param mode: Режим работы:
|
||||
- None / False: отключено.
|
||||
- 'left': только левая пунктуация.
|
||||
- 'right': только правая пунктуация.
|
||||
- 'both' / True: и левая, и правая.
|
||||
- 'left': левая висячая пунктуация.
|
||||
- 'right': правая висячая пунктуация.
|
||||
- list[str]: список тегов (например, ['p', 'blockquote']),
|
||||
внутри которых применять 'both'.
|
||||
внутри которых применять висячую пунктуацию в обе стороны.
|
||||
- True эквивалентно 'left'.
|
||||
"""
|
||||
self.mode = mode
|
||||
self.target_tags = None
|
||||
self.active_chars = set()
|
||||
|
||||
# Определяем, какие символы будем обрабатывать
|
||||
if isinstance(mode, list):
|
||||
self.target_tags = set(t.lower() for t in mode)
|
||||
# Если передан список тегов, включаем полный режим ('both') внутри них
|
||||
self.active_chars.update(HANGING_PUNCTUATION_LEFT_CHARS)
|
||||
self.active_chars.update(HANGING_PUNCTUATION_RIGHT_CHARS)
|
||||
elif mode == 'left':
|
||||
self.active_chars.update(HANGING_PUNCTUATION_LEFT_CHARS)
|
||||
elif mode == 'right':
|
||||
self.active_chars.update(HANGING_PUNCTUATION_RIGHT_CHARS)
|
||||
elif mode == 'both' or mode is True:
|
||||
else:
|
||||
normalized_mode = HANGING_PUNCTUATION_MODE_LEFT if mode is True else mode
|
||||
if normalized_mode == HANGING_PUNCTUATION_MODE_LEFT:
|
||||
self.active_chars.update(HANGING_PUNCTUATION_LEFT_CHARS)
|
||||
elif normalized_mode == HANGING_PUNCTUATION_MODE_RIGHT:
|
||||
self.active_chars.update(HANGING_PUNCTUATION_RIGHT_CHARS)
|
||||
|
||||
# Предварительно фильтруем карту классов, оставляя только активные символы
|
||||
|
||||
@@ -34,7 +34,7 @@ dependencies = [
|
||||
"Homepage" = "https://github.com/erjemin/etpgrf"
|
||||
"Bug Tracker" = "https://github.com/erjemin/etpgrf/issues"
|
||||
"Mirror1 (GitVerse)" = "https://gitverse.ru/erjemin/etpgrf"
|
||||
"Nirror2 (Gitea Selfhosted)" = "https://git.cube2.ru/erjemin/2025-etpgrf"
|
||||
"Mirror2 (Gitea Selfhosted)" = "https://git.cube2.ru/erjemin/2025-etpgrf"
|
||||
|
||||
[tool.setuptools.packages.find]
|
||||
where = ["."] # Искать пакеты в корне (найдет папку etpgrf)
|
||||
|
||||
@@ -19,60 +19,34 @@ HANGING_TEST_CASES = [
|
||||
# --- Режим 'left' (только левая пунктуация) ---
|
||||
('left', f'<p>{CHAR_RU_QUOT1_OPEN}Цитата{CHAR_RU_QUOT1_CLOSE}</p>',
|
||||
f'<p><span class="etp-laquo">{CHAR_RU_QUOT1_OPEN}</span>Цитата{CHAR_RU_QUOT1_CLOSE}</p>'),
|
||||
('left', f'<p>(Скобки)</p>',
|
||||
f'<p><span class="etp-lpar">(</span>Скобки)</p>'),
|
||||
# Правая пунктуация игнорируется
|
||||
('left', f'<p>Текст.</p>', f'<p>Текст.</p>'),
|
||||
('left', '<p>(Скобки)</p>', '<p><span class="etp-lpar">(</span>Скобки)</p>'),
|
||||
('left', '<p>Текст.</p>', '<p>Текст.</p>'),
|
||||
|
||||
# --- Режим 'right' (только правая пунктуация) ---
|
||||
('right', f'<p>{CHAR_RU_QUOT1_OPEN}Цитата{CHAR_RU_QUOT1_CLOSE}</p>',
|
||||
f'<p>{CHAR_RU_QUOT1_OPEN}Цитата<span class="etp-raquo">{CHAR_RU_QUOT1_CLOSE}</span></p>'),
|
||||
('right', f'<p>Текст.</p>',
|
||||
f'<p>Текст<span class="etp-r-dot">.</span></p>'),
|
||||
# Левая пунктуация игнорируется
|
||||
('right', f'<p>(Скобки)</p>', f'<p>(Скобки<span class="etp-rpar">)</span></p>'),
|
||||
|
||||
# --- Режим 'both' (и левая, и правая) ---
|
||||
('both', f'<p>{CHAR_RU_QUOT1_OPEN}Цитата{CHAR_RU_QUOT1_CLOSE}</p>',
|
||||
f'<p><span class="etp-laquo">{CHAR_RU_QUOT1_OPEN}</span>Цитата<span class="etp-raquo">{CHAR_RU_QUOT1_CLOSE}</span></p>'),
|
||||
('both', f'<p>Текст.</p>',
|
||||
f'<p>Текст<span class="etp-r-dot">.</span></p>'),
|
||||
# Последовательность символов (точка + кавычка)
|
||||
('both', f'<p>Текст.{CHAR_RU_QUOT1_CLOSE}</p>',
|
||||
f'<p>Текст<span class="etp-r-dot">.</span><span class="etp-raquo">{CHAR_RU_QUOT1_CLOSE}</span></p>'),
|
||||
# Вложенные теги
|
||||
('both', f'<p><b>{CHAR_RU_QUOT1_OPEN}Жирный{CHAR_RU_QUOT1_CLOSE}</b></p>',
|
||||
f'<p><b><span class="etp-laquo">{CHAR_RU_QUOT1_OPEN}</span>Жирный<span class="etp-raquo">{CHAR_RU_QUOT1_CLOSE}</span></b></p>'),
|
||||
# Смешанный контент
|
||||
('both', f'<p>{CHAR_RU_QUOT1_OPEN}Начало <i>курсив</i> конец.{CHAR_RU_QUOT1_CLOSE}</p>',
|
||||
f'<p><span class="etp-laquo">{CHAR_RU_QUOT1_OPEN}</span>Начало <i>курсив</i> конец<span class="etp-r-dot">.</span><span class="etp-raquo">{CHAR_RU_QUOT1_CLOSE}</span></p>'),
|
||||
('right', '<p>Текст.</p>', '<p>Текст<span class="etp-r-dot">.</span></p>'),
|
||||
('right', '<p>(Скобки)</p>', '<p>(Скобки<span class="etp-rpar">)</span></p>'),
|
||||
('right', '<p>3.14</p>', '<p>3.14</p>'),
|
||||
('right', '<p>End.</p>', '<p>End<span class="etp-r-dot">.</span></p>'),
|
||||
|
||||
# --- Режим None / False (отключено) ---
|
||||
(None, f'<p>{CHAR_RU_QUOT1_OPEN}Текст{CHAR_RU_QUOT1_CLOSE}</p>',
|
||||
f'<p>{CHAR_RU_QUOT1_OPEN}Текст{CHAR_RU_QUOT1_CLOSE}</p>'),
|
||||
(False, f'<p>{CHAR_RU_QUOT1_OPEN}Текст{CHAR_RU_QUOT1_CLOSE}</p>',
|
||||
f'<p>{CHAR_RU_QUOT1_OPEN}Текст{CHAR_RU_QUOT1_CLOSE}</p>'),
|
||||
]
|
||||
|
||||
# --- Отсутствие висячих символов ---
|
||||
('both', '<p>Простой текст без спецсимволов!</p>', '<p>Простой текст без спецсимволов!</p>'),
|
||||
|
||||
# --- Проверка контекста (пробелы) ---
|
||||
# 1. Левая кавычка внутри слова (не должна висеть)
|
||||
('both', f'<p>func{CHAR_RU_QUOT1_OPEN}arg{CHAR_RU_QUOT1_CLOSE}</p>',
|
||||
f'<p>func{CHAR_RU_QUOT1_OPEN}arg<span class="etp-raquo">{CHAR_RU_QUOT1_CLOSE}</span></p>'), # Правая висит, т.к. конец узла
|
||||
# 2. Правая кавычка внутри слова (не должна висеть)
|
||||
('both', f'<p>1{CHAR_RU_QUOT1_CLOSE}2</p>',
|
||||
f'<p>1{CHAR_RU_QUOT1_CLOSE}2</p>'),
|
||||
# 3. Левая кавычка после пробела (должна висеть)
|
||||
('both', f'<p>func {CHAR_RU_QUOT1_OPEN}arg</p>',
|
||||
# --- Режим list[str] (список тегов с обеими сторонами) ---
|
||||
HANGING_LIST_MODE_CASES = [
|
||||
(['p'], f'<p>{CHAR_RU_QUOT1_OPEN}Цитата{CHAR_RU_QUOT1_CLOSE}</p>',
|
||||
f'<p><span class="etp-laquo">{CHAR_RU_QUOT1_OPEN}</span>Цитата<span class="etp-raquo">{CHAR_RU_QUOT1_CLOSE}</span></p>'),
|
||||
(['p'], f'<p>Текст.{CHAR_RU_QUOT1_CLOSE}</p>',
|
||||
f'<p>Текст<span class="etp-r-dot">.</span><span class="etp-raquo">{CHAR_RU_QUOT1_CLOSE}</span></p>'),
|
||||
(['p'], f'<p>func {CHAR_RU_QUOT1_OPEN}arg</p>',
|
||||
f'<p>func <span class="etp-laquo">{CHAR_RU_QUOT1_OPEN}</span>arg</p>'),
|
||||
# 4. Правая кавычка перед пробелом (должна висеть)
|
||||
('both', f'<p>arg{CHAR_RU_QUOT1_CLOSE} next</p>',
|
||||
(['p'], f'<p>arg{CHAR_RU_QUOT1_CLOSE} next</p>',
|
||||
f'<p>arg<span class="etp-raquo">{CHAR_RU_QUOT1_CLOSE}</span> next</p>'),
|
||||
# 5. Точка внутри числа (не должна висеть)
|
||||
('both', '<p>3.14</p>', '<p>3.14</p>'),
|
||||
# 6. Точка в конце предложения (должна висеть)
|
||||
('both', '<p>End.</p>', '<p>End<span class="etp-r-dot">.</span></p>'),
|
||||
]
|
||||
|
||||
|
||||
@@ -112,3 +86,13 @@ def test_hanging_punctuation_target_tags():
|
||||
processor.process(soup)
|
||||
|
||||
assert str(soup) == expected_html
|
||||
|
||||
|
||||
@pytest.mark.parametrize("mode, input_html, expected_html", HANGING_LIST_MODE_CASES)
|
||||
def test_hanging_punctuation_processor_list_mode(mode, input_html, expected_html):
|
||||
"""Проверяет, что list-режим работает и для левой, и для правой стороны внутри указанного тега."""
|
||||
processor = HangingPunctuationProcessor(mode=mode)
|
||||
soup = make_soup(input_html)
|
||||
|
||||
processor.process(soup)
|
||||
assert str(soup) == expected_html
|
||||
|
||||
Reference in New Issue
Block a user