From 5948305ce1d302fb9be6144e1c690e08cbfc28fc Mon Sep 17 00:00:00 2001 From: Eapp1e <163811520+Eapp1e@users.noreply.github.com> Date: Wed, 28 Oct 2026 05:06:33 +0000 Subject: [PATCH] fix(asciidoc): open blocks on four or more delimiters AsciiDoc treats the delimiter length as a minimum: listing blocks open with four or more hyphens and literal blocks with four or more dots. The backend only accepted exactly four, so a block opened with five or more delimiters was parsed as a paragraph and its content leaked into running text. Match four or more and keep the real delimiter line so the closing delimiter is still found. Signed-off-by: Eapp1e <163811520+Eapp1e@users.noreply.github.com> --- docling/backend/asciidoc_backend.py | 18 ++++++++++++++---- tests/test_backend_asciidoc.py | 21 +++++++++++++++++++++ 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/docling/backend/asciidoc_backend.py b/docling/backend/asciidoc_backend.py index 63c984b44b..130a7c7ddb 100644 --- a/docling/backend/asciidoc_backend.py +++ b/docling/backend/asciidoc_backend.py @@ -41,6 +41,12 @@ # to an identical line is a comment. _LINE_COMMENT_RE = re.compile(r"^//(?!/)") _COMMENT_BLOCK_DELIMITER_RE = re.compile(r"/{4,}") +# A line of four or more hyphens opens a listing block and a line of four or +# more dots opens a literal block: the count is a minimum, so "-----" opens a +# listing just as "----" does. +_VERBATIM_DELIMITER_RE = re.compile(r"-{4,}|\.{4,}") +# A listing block takes a "[source,lang]" attribute; a literal block does not. +_LISTING_DELIMITER_RE = re.compile(r"-{4,}") # Cell format specifier that may precede a "|" delimiter, e.g. "^.^h" in # "^.^h|Header": span (3*, 2+, .2+, 2.3+), alignment (<, ^, >, .^), style @@ -567,9 +573,9 @@ def _iter_blocks( yield comment continue - if stripped in {"....", "----"}: + if _VERBATIM_DELIMITER_RE.fullmatch(stripped): # "...." is a literal block; "----" a listing block. A - # "[source,lang]" attribute consumed just before a "----" + # "[source,lang]" attribute consumed just before a listing # labels the listing's language. block_data = [] block_delimiter = stripped @@ -597,9 +603,13 @@ def _iter_blocks( continue source_attr = cls._SOURCE_ATTR_RE.match(stripped) - if source_attr is not None and i + 1 < n and lines[i + 1].strip() == "----": + if ( + source_attr is not None + and i + 1 < n + and _LISTING_DELIMITER_RE.fullmatch(lines[i + 1].strip()) + ): block_data = [] - block_delimiter = "----" + block_delimiter = lines[i + 1].strip() block_language = source_attr.group(1) i += 2 continue diff --git a/tests/test_backend_asciidoc.py b/tests/test_backend_asciidoc.py index 3b227cf375..302ae3b344 100644 --- a/tests/test_backend_asciidoc.py +++ b/tests/test_backend_asciidoc.py @@ -247,6 +247,27 @@ def test_source_listing_block_becomes_code_item() -> None: assert [item.text for item in doc.texts if item.label != DocItemLabel.CODE] == [] +def test_delimiter_longer_than_four_characters_opens_the_block() -> None: + # A listing block opens on four or more hyphens and a literal block on four + # or more dots, so the marker of a valid block is not always four characters + # long. Only "----" and "...." opened a block, so the markers of a longer + # block stayed in the document as paragraphs and the code became prose. + src = b"[source,python]\n-----\nx = 1\n-----\n\n.....\nraw\n.....\n" + in_doc = InputDocument( + path_or_stream=BytesIO(src), + format=InputFormat.ASCIIDOC, + backend=AsciiDocBackend, + filename="long-delimiter.adoc", + ) + doc = in_doc._backend.convert() + + code_items = [item for item in doc.texts if item.label == DocItemLabel.CODE] + assert [item.text for item in code_items] == ["x = 1", "raw"] + assert code_items[0].code_language == CodeLanguageLabel.PYTHON + # no marker or attribute text may leak into the body + assert [item.text for item in doc.texts if item.label != DocItemLabel.CODE] == [] + + def test_listing_block_without_language_stays_code() -> None: src = b"----\nplain listing\n----\n" in_doc = InputDocument(