fix: исправлено удаление двойного экранирования &
This commit is contained in:
@@ -73,6 +73,7 @@ CHAR_MIDDOT = '\u00b7' # Средняя точка (· иногда испо
|
|||||||
CHAR_UNIT_SEPARATOR = '\u25F0' # Символ временный разделитель для составных единиц (◰), чтобы не уходить
|
CHAR_UNIT_SEPARATOR = '\u25F0' # Символ временный разделитель для составных единиц (◰), чтобы не уходить
|
||||||
# в "мертвый" цикл при замене на тонкий пробел. Можно взять любой редкий символом.
|
# в "мертвый" цикл при замене на тонкий пробел. Можно взять любой редкий символом.
|
||||||
CHAR_PLACEHOLDER = '\uFFFC' # Уникальная строка-заполнитель для защищенных тегов.
|
CHAR_PLACEHOLDER = '\uFFFC' # Уникальная строка-заполнитель для защищенных тегов.
|
||||||
|
CHAR_AMP_PLACEHOLDER = '\uFFFD' # Маркер-плейсхолдер для амперсанда (&), чтобы избежать его двойного кодирования в & при замене на мнемонику.
|
||||||
CHAR_NODE_SEPARATOR = '\uFFFF' # Маркер границы текстовых узлов (Non-character).
|
CHAR_NODE_SEPARATOR = '\uFFFF' # Маркер границы текстовых узлов (Non-character).
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ from etpgrf.symbols import SymbolsProcessor
|
|||||||
from etpgrf.sanitizer import SanitizerProcessor
|
from etpgrf.sanitizer import SanitizerProcessor
|
||||||
from etpgrf.hanging import HangingPunctuationProcessor
|
from etpgrf.hanging import HangingPunctuationProcessor
|
||||||
from etpgrf.codec import decode_to_unicode, encode_from_unicode
|
from etpgrf.codec import decode_to_unicode, encode_from_unicode
|
||||||
from etpgrf.config import PROTECTED_HTML_TAGS, CHAR_PLACEHOLDER, CHAR_NODE_SEPARATOR
|
from etpgrf.config import PROTECTED_HTML_TAGS, CHAR_PLACEHOLDER, CHAR_NODE_SEPARATOR, CHAR_AMP_PLACEHOLDER
|
||||||
|
|
||||||
|
|
||||||
# --- Настройки логирования ---
|
# --- Настройки логирования ---
|
||||||
@@ -239,6 +239,12 @@ class Typographer:
|
|||||||
"""
|
"""
|
||||||
if not text:
|
if not text:
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
# --- ЭТАП 0: Защита & ---
|
||||||
|
# Заменяем & на временный плейсхолдер, чтобы он не был декодирован в &
|
||||||
|
# и не был повторно закодирован в &
|
||||||
|
text = text.replace('&', CHAR_AMP_PLACEHOLDER)
|
||||||
|
|
||||||
# Если включена обработка HTML и BeautifulSoup доступен
|
# Если включена обработка HTML и BeautifulSoup доступен
|
||||||
if self.process_html:
|
if self.process_html:
|
||||||
# --- ЭТАП 1: Анализ структуры ---
|
# --- ЭТАП 1: Анализ структуры ---
|
||||||
@@ -287,6 +293,7 @@ class Typographer:
|
|||||||
super_string = ""
|
super_string = ""
|
||||||
# lengths_map больше не нужен, так как мы используем разделители
|
# lengths_map больше не нужен, так как мы используем разделители
|
||||||
|
|
||||||
|
super_string = ""
|
||||||
for node in text_nodes:
|
for node in text_nodes:
|
||||||
# ВАЖНО: Используем node.string (Unicode), а не str(node) (HTML-encoded).
|
# ВАЖНО: Используем node.string (Unicode), а не str(node) (HTML-encoded).
|
||||||
# str(node) может вернуть экранированные символы (например, < вместо <),
|
# str(node) может вернуть экранированные символы (например, < вместо <),
|
||||||
@@ -364,9 +371,11 @@ class Typographer:
|
|||||||
|
|
||||||
# BeautifulSoup по умолчанию экранирует амперсанды (& -> &), которые мы сгенерировали
|
# BeautifulSoup по умолчанию экранирует амперсанды (& -> &), которые мы сгенерировали
|
||||||
# в _process_text_node. Возвращаем их обратно.
|
# в _process_text_node. Возвращаем их обратно.
|
||||||
return processed_html.replace('&', '&')
|
processed_text = processed_html.replace('&', '&')
|
||||||
else:
|
else:
|
||||||
return self._process_plain_text(text)
|
# Для простого текста тоже нужна защита &
|
||||||
|
processed_text = self._process_plain_text(text)
|
||||||
|
return processed_text.replace(CHAR_AMP_PLACEHOLDER, '&')
|
||||||
|
|
||||||
def _process_plain_text(self, text: str) -> str:
|
def _process_plain_text(self, text: str) -> str:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -177,10 +177,15 @@ HTML_STRUCTURE_TEST_CASES = [
|
|||||||
('<ul><li>Исправлена проблема с появлением лишних тегов <code><html></code> и <code><body></code> при обработке фрагментов HTML.</li></ul><h5>Заголовок</h5>',
|
('<ul><li>Исправлена проблема с появлением лишних тегов <code><html></code> и <code><body></code> при обработке фрагментов HTML.</li></ul><h5>Заголовок</h5>',
|
||||||
'<ul><li>Исправлена проблема с появлением лишних тегов <code><html></code> и <code><body></code> при обработке фрагментов HTML.</li></ul><h5>Заголовок</h5>'),
|
'<ul><li>Исправлена проблема с появлением лишних тегов <code><html></code> и <code><body></code> при обработке фрагментов HTML.</li></ul><h5>Заголовок</h5>'),
|
||||||
|
|
||||||
# 6/ Исправленный тест на защищенные теги с немаскированными HTML внутри
|
# 6. Исправленный тест на защищенные теги с немаскированными HTML внутри
|
||||||
# (все незакрытые теги будут закрыты через BS, а тег <html> удалены)
|
# (все незакрытые теги будут закрыты через BS, а тег <html> удалены)
|
||||||
('<ul><li>Исправлена проблема\n с появлением лишних тегов <code><html>++</html></code> и <code><body&></code> при обработке фрагментов HTML.</li></ul><h5>Заголовок</h5>',
|
('<ul><li>Исправлена проблема\n с появлением лишних тегов <code><html>++</html></code> и <code><body&></code> при обработке фрагментов HTML.</li></ul><h5>Заголовок</h5>',
|
||||||
'<ul><li>Исправлена проблема\n с появлением лишних тегов <code>++</code> и <code><body&></body&></code> при обработке фрагментов HTML.</li></ul><h5>Заголовок</h5>'),
|
'<ul><li>Исправлена проблема\n с появлением лишних тегов <code>++</code> и <code><body&></body&></code> при обработке фрагментов HTML.</li></ul><h5>Заголовок</h5>'),
|
||||||
|
|
||||||
|
# 7. Тест на маскированные мнемоники и де-экранирование &
|
||||||
|
('<p>Текст с < и > и & внутри.</p>', '<p>Текст с < и > и & внутри.</p>'),
|
||||||
|
('<p>Текст с &lt; и &gt; и &amp; внутри.</p>', '<p>Текст с &lt; и &gt; и &amp; внутри.</p>'),
|
||||||
|
('<p>Мнемоника <code>&nbsp;</code> превратится в неразрывный пробел</p>', '<p>Мнемоника <code>&nbsp;</code> превратится в неразрывный пробел</p>'),
|
||||||
]
|
]
|
||||||
|
|
||||||
@pytest.mark.parametrize("input_html, expected_html", HTML_STRUCTURE_TEST_CASES)
|
@pytest.mark.parametrize("input_html, expected_html", HTML_STRUCTURE_TEST_CASES)
|
||||||
|
|||||||
Reference in New Issue
Block a user