diff --git a/src/pymax/formatting/markdown.py b/src/pymax/formatting/markdown.py index d84cbdf..868f9d5 100644 --- a/src/pymax/formatting/markdown.py +++ b/src/pymax/formatting/markdown.py @@ -51,6 +51,29 @@ def _parse_link( return label, url, url_end + 1 + @staticmethod + def _is_intraword_single_marker(text: str, i: int, marker: str) -> bool: + if marker not in {"_", "*"}: + return False + if i == 0 or i + 1 >= len(text): + return False + return text[i - 1].isalnum() and text[i + 1].isalnum() + + @staticmethod + def _find_closing_marker( + text: str, + marker: str, + start: int, + end: int | None = None, + ) -> int: + while True: + closing_index = text.find(marker, start, len(text) if end is None else end) + if closing_index == -1: + return -1 + if not Formatter._is_intraword_single_marker(text, closing_index, marker): + return closing_index + start = closing_index + len(marker) + @staticmethod def format_markdown(text: str) -> tuple[str, list[Element]]: clean_text = "" @@ -155,6 +178,8 @@ def format_markdown(text: str) -> tuple[str, list[Element]]: for marker in Formatter.MARKER_ORDER: if not text.startswith(marker, i): continue + if Formatter._is_intraword_single_marker(text, i, marker): + continue marker_len = len(marker) @@ -180,7 +205,8 @@ def format_markdown(text: str) -> tuple[str, list[Element]]: break end = text.find("\n", i + marker_len) - closing_index = text.find( + closing_index = Formatter._find_closing_marker( + text, marker, i + marker_len, None if end == -1 else end, diff --git a/tests/files/test_files_and_formatting.py b/tests/files/test_files_and_formatting.py index d73a3eb..fcdde03 100644 --- a/tests/files/test_files_and_formatting.py +++ b/tests/files/test_files_and_formatting.py @@ -70,3 +70,29 @@ def test_markdown_formatter_extracts_functional_entities() -> None: ] assert entities[-1].attributes is not None assert entities[-1].attributes.url == "https://example.com" + + +def test_markdown_formatter_preserves_intraword_single_markers() -> None: + text = "https://max.ru/channel_iclub_new snake_case_name цена 5*2 = 10" + + clean, entities = Formatter.format_markdown(text) + + assert clean == text + assert entities == [] + + +def test_markdown_formatter_preserves_unclosed_single_marker_before_intraword_marker() -> None: + clean, entities = Formatter.format_markdown("_foo_bar") + + assert clean == "_foo_bar" + assert entities == [] + + +def test_markdown_formatter_still_parses_delimited_single_marker_emphasis() -> None: + clean, entities = Formatter.format_markdown("Hello _world_ and *again*") + + assert clean == "Hello world and again" + assert [(entity.type, entity.from_, entity.length) for entity in entities] == [ + ("EMPHASIZED", 6, 5), + ("EMPHASIZED", 16, 5), + ]