mod: Отключена опция 'both' для висячей пунктуации
This commit is contained in:
@@ -356,7 +356,7 @@ typo = etpgrf.Typographer(hanging_punctuation='left')
|
|||||||
|
|
||||||
### CSS для висячих символов
|
### CSS для висячих символов
|
||||||
|
|
||||||
Предлагаемый CSS теперь работает только с `margin` и `padding`, без `position:absolute`. Пробелы получают собственные
|
Предлагаемый, начиная с etpgrf v0.1.6, CSS теперь работает только с `margin` и `padding`, без `position:absolute`. Пробелы получают собственные
|
||||||
классы, поэтому их компенсация контролируется отдельно, а не встроена в сам висячий символ. Убедитесь, что эти стили
|
классы, поэтому их компенсация контролируется отдельно, а не встроена в сам висячий символ. Убедитесь, что эти стили
|
||||||
подключены к странице и не конфликтуют с `text-justify`, который вытягивает пробелы по всей строке и разрушает аккуратное
|
подключены к странице и не конфликтуют с `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. Набор символов, которые могут "висеть" слева
|
# 1. Набор символов, которые могут "висеть" слева
|
||||||
HANGING_PUNCTUATION_LEFT_CHARS = frozenset([
|
HANGING_PUNCTUATION_LEFT_CHARS = frozenset([
|
||||||
CHAR_RU_QUOT1_OPEN, # «
|
CHAR_RU_QUOT1_OPEN, # «
|
||||||
@@ -723,3 +730,4 @@ HANGING_PUNCTUATION_CLASSES = {
|
|||||||
',': 'etp-r-comma',
|
',': 'etp-r-comma',
|
||||||
':': 'etp-r-colon',
|
':': 'etp-r-colon',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,9 @@ from bs4 import BeautifulSoup, NavigableString, Tag
|
|||||||
from .config import (
|
from .config import (
|
||||||
HANGING_PUNCTUATION_LEFT_CHARS,
|
HANGING_PUNCTUATION_LEFT_CHARS,
|
||||||
HANGING_PUNCTUATION_RIGHT_CHARS,
|
HANGING_PUNCTUATION_RIGHT_CHARS,
|
||||||
HANGING_PUNCTUATION_CLASSES
|
HANGING_PUNCTUATION_CLASSES,
|
||||||
|
HANGING_PUNCTUATION_MODE_LEFT,
|
||||||
|
HANGING_PUNCTUATION_MODE_RIGHT,
|
||||||
)
|
)
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -21,28 +23,25 @@ class HangingPunctuationProcessor:
|
|||||||
"""
|
"""
|
||||||
:param mode: Режим работы:
|
:param mode: Режим работы:
|
||||||
- None / False: отключено.
|
- None / False: отключено.
|
||||||
- 'left': только левая пунктуация.
|
- 'left': левая висячая пунктуация.
|
||||||
- 'right': только правая пунктуация.
|
- 'right': правая висячая пунктуация.
|
||||||
- 'both' / True: и левая, и правая.
|
|
||||||
- list[str]: список тегов (например, ['p', 'blockquote']),
|
- list[str]: список тегов (например, ['p', 'blockquote']),
|
||||||
внутри которых применять 'both'.
|
внутри которых применять висячую пунктуацию в обе стороны.
|
||||||
|
- True эквивалентно 'left'.
|
||||||
"""
|
"""
|
||||||
self.mode = mode
|
self.mode = mode
|
||||||
self.target_tags = None
|
self.target_tags = None
|
||||||
self.active_chars = set()
|
self.active_chars = set()
|
||||||
|
|
||||||
# Определяем, какие символы будем обрабатывать
|
|
||||||
if isinstance(mode, list):
|
if isinstance(mode, list):
|
||||||
self.target_tags = set(t.lower() for t in mode)
|
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_LEFT_CHARS)
|
||||||
self.active_chars.update(HANGING_PUNCTUATION_RIGHT_CHARS)
|
self.active_chars.update(HANGING_PUNCTUATION_RIGHT_CHARS)
|
||||||
elif mode == 'left':
|
else:
|
||||||
self.active_chars.update(HANGING_PUNCTUATION_LEFT_CHARS)
|
normalized_mode = HANGING_PUNCTUATION_MODE_LEFT if mode is True else mode
|
||||||
elif mode == 'right':
|
if normalized_mode == HANGING_PUNCTUATION_MODE_LEFT:
|
||||||
self.active_chars.update(HANGING_PUNCTUATION_RIGHT_CHARS)
|
|
||||||
elif mode == 'both' or mode is True:
|
|
||||||
self.active_chars.update(HANGING_PUNCTUATION_LEFT_CHARS)
|
self.active_chars.update(HANGING_PUNCTUATION_LEFT_CHARS)
|
||||||
|
elif normalized_mode == HANGING_PUNCTUATION_MODE_RIGHT:
|
||||||
self.active_chars.update(HANGING_PUNCTUATION_RIGHT_CHARS)
|
self.active_chars.update(HANGING_PUNCTUATION_RIGHT_CHARS)
|
||||||
|
|
||||||
# Предварительно фильтруем карту классов, оставляя только активные символы
|
# Предварительно фильтруем карту классов, оставляя только активные символы
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ dependencies = [
|
|||||||
"Homepage" = "https://github.com/erjemin/etpgrf"
|
"Homepage" = "https://github.com/erjemin/etpgrf"
|
||||||
"Bug Tracker" = "https://github.com/erjemin/etpgrf/issues"
|
"Bug Tracker" = "https://github.com/erjemin/etpgrf/issues"
|
||||||
"Mirror1 (GitVerse)" = "https://gitverse.ru/erjemin/etpgrf"
|
"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]
|
[tool.setuptools.packages.find]
|
||||||
where = ["."] # Искать пакеты в корне (найдет папку etpgrf)
|
where = ["."] # Искать пакеты в корне (найдет папку etpgrf)
|
||||||
|
|||||||
@@ -19,60 +19,34 @@ HANGING_TEST_CASES = [
|
|||||||
# --- Режим 'left' (только левая пунктуация) ---
|
# --- Режим 'left' (только левая пунктуация) ---
|
||||||
('left', f'<p>{CHAR_RU_QUOT1_OPEN}Цитата{CHAR_RU_QUOT1_CLOSE}</p>',
|
('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>'),
|
f'<p><span class="etp-laquo">{CHAR_RU_QUOT1_OPEN}</span>Цитата{CHAR_RU_QUOT1_CLOSE}</p>'),
|
||||||
('left', f'<p>(Скобки)</p>',
|
('left', '<p>(Скобки)</p>', '<p><span class="etp-lpar">(</span>Скобки)</p>'),
|
||||||
f'<p><span class="etp-lpar">(</span>Скобки)</p>'),
|
('left', '<p>Текст.</p>', '<p>Текст.</p>'),
|
||||||
# Правая пунктуация игнорируется
|
|
||||||
('left', f'<p>Текст.</p>', f'<p>Текст.</p>'),
|
|
||||||
|
|
||||||
# --- Режим 'right' (только правая пунктуация) ---
|
# --- Режим 'right' (только правая пунктуация) ---
|
||||||
('right', f'<p>{CHAR_RU_QUOT1_OPEN}Цитата{CHAR_RU_QUOT1_CLOSE}</p>',
|
('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>'),
|
f'<p>{CHAR_RU_QUOT1_OPEN}Цитата<span class="etp-raquo">{CHAR_RU_QUOT1_CLOSE}</span></p>'),
|
||||||
('right', f'<p>Текст.</p>',
|
('right', '<p>Текст.</p>', '<p>Текст<span class="etp-r-dot">.</span></p>'),
|
||||||
f'<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', f'<p>(Скобки)</p>', f'<p>(Скобки<span class="etp-rpar">)</span></p>'),
|
('right', '<p>End.</p>', '<p>End<span class="etp-r-dot">.</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>'),
|
|
||||||
|
|
||||||
# --- Режим None / False (отключено) ---
|
# --- Режим None / False (отключено) ---
|
||||||
(None, f'<p>{CHAR_RU_QUOT1_OPEN}Текст{CHAR_RU_QUOT1_CLOSE}</p>',
|
(None, f'<p>{CHAR_RU_QUOT1_OPEN}Текст{CHAR_RU_QUOT1_CLOSE}</p>',
|
||||||
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>',
|
(False, f'<p>{CHAR_RU_QUOT1_OPEN}Текст{CHAR_RU_QUOT1_CLOSE}</p>',
|
||||||
f'<p>{CHAR_RU_QUOT1_OPEN}Текст{CHAR_RU_QUOT1_CLOSE}</p>'),
|
f'<p>{CHAR_RU_QUOT1_OPEN}Текст{CHAR_RU_QUOT1_CLOSE}</p>'),
|
||||||
|
]
|
||||||
|
|
||||||
# --- Отсутствие висячих символов ---
|
# --- Режим list[str] (список тегов с обеими сторонами) ---
|
||||||
('both', '<p>Простой текст без спецсимволов!</p>', '<p>Простой текст без спецсимволов!</p>'),
|
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>'),
|
||||||
# 1. Левая кавычка внутри слова (не должна висеть)
|
(['p'], f'<p>Текст.{CHAR_RU_QUOT1_CLOSE}</p>',
|
||||||
('both', f'<p>func{CHAR_RU_QUOT1_OPEN}arg{CHAR_RU_QUOT1_CLOSE}</p>',
|
f'<p>Текст<span class="etp-r-dot">.</span><span class="etp-raquo">{CHAR_RU_QUOT1_CLOSE}</span></p>'),
|
||||||
f'<p>func{CHAR_RU_QUOT1_OPEN}arg<span class="etp-raquo">{CHAR_RU_QUOT1_CLOSE}</span></p>'), # Правая висит, т.к. конец узла
|
(['p'], f'<p>func {CHAR_RU_QUOT1_OPEN}arg</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>',
|
|
||||||
f'<p>func <span class="etp-laquo">{CHAR_RU_QUOT1_OPEN}</span>arg</p>'),
|
f'<p>func <span class="etp-laquo">{CHAR_RU_QUOT1_OPEN}</span>arg</p>'),
|
||||||
# 4. Правая кавычка перед пробелом (должна висеть)
|
(['p'], f'<p>arg{CHAR_RU_QUOT1_CLOSE} next</p>',
|
||||||
('both', f'<p>arg{CHAR_RU_QUOT1_CLOSE} next</p>',
|
|
||||||
f'<p>arg<span class="etp-raquo">{CHAR_RU_QUOT1_CLOSE}</span> 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)
|
processor.process(soup)
|
||||||
|
|
||||||
assert str(soup) == expected_html
|
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