Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion docling/utils/chandra_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -589,7 +589,8 @@ def _block(
if re.fullmatch(r"h[1-6]", node.tag):
for item in self.doc.texts[before:]:
if isinstance(item, SectionHeaderItem):
item.level = int(node.tag[1])
# Docling section level 1 exports as H2, below the title.
item.level = max(1, int(node.tag[1]) - 1)

def _list(self, node: _Element, parent: NodeItem | None) -> None:
group = self.doc.add_list_group(parent=parent)
Expand Down
6 changes: 5 additions & 1 deletion docling/utils/nemotron_parse_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,11 @@ def parse_nemotron_parse_v2(
)
elif region.label == "Section-header":
heading_match = _HEADING_PATTERN.match(raw_text)
level = len(heading_match.group(1)) if heading_match is not None else 1
level = (
max(1, len(heading_match.group(1)) - 1)
if heading_match is not None
else 1
)
document.add_heading(
text=_markdown_to_text(raw_text),
orig=raw_text,
Expand Down
2 changes: 1 addition & 1 deletion tests/test_chandra_vlm.py
Original file line number Diff line number Diff line change
Expand Up @@ -394,7 +394,7 @@ def test_chandra_inline_math_formatting_and_code_whitespace():
str(next(t.hyperlink for t in doc.texts if t.text == "link"))
== "https://example.org/"
)
assert doc.texts[-1].level == 3
assert doc.texts[-1].level == 2


def test_chandra_nested_math_does_not_inherit_table_bbox():
Expand Down
2 changes: 1 addition & 1 deletion tests/test_nemotron_parse_v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ def test_parse_nemotron_markdown_and_semantic_classes() -> None:
assert document.texts[0].label == DocItemLabel.TITLE
assert document.texts[0].text == "Main title"
assert document.texts[1].label == DocItemLabel.SECTION_HEADER
assert document.texts[1].level == 3
assert document.texts[1].level == 2
assert document.texts[1].text == "1.2 Method"
assert document.texts[2].text == "Article history:\nReceived 12 June"
assert any(item.label == DocItemLabel.REFERENCE for item in document.texts)
Expand Down
56 changes: 56 additions & 0 deletions tests/test_vlm_heading_exports.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# SPDX-FileCopyrightText: The Docling Contributors
# SPDX-License-Identifier: MIT

"""Heading depth must survive VLM response parsing and Markdown export."""

import pytest
from docling_core.types.doc import (
DocItemLabel,
DoclingDocument,
SectionHeaderItem,
Size,
)

from docling.utils.chandra_utils import _add_html_fragment, parse_chandra_html
from docling.utils.nemotron_parse_utils import parse_nemotron_parse_v2


@pytest.mark.parametrize("parser", ["chandra", "nemotron", "html_fragment"])
@pytest.mark.parametrize("depth", [2, 3, 6])
def test_heading_depth_survives_export(parser: str, depth: int) -> None:
size = Size(width=600, height=800)
html = (
'<div data-bbox="100 100 900 200" data-label="Section-Header">'
f"<h{depth}>Method</h{depth}></div>"
)
if parser == "chandra":
doc = parse_chandra_html(html, size, page_no=2)
elif parser == "nemotron":
source = (
f"<x_0.2><y_0.2>{'#' * depth} Method<x_0.8><y_0.4><class_Section-header>"
)
doc = parse_nemotron_parse_v2(source, size, size, page_no=2)
else:
doc = DoclingDocument(name="fragment")
doc.add_page(page_no=2, size=size)
_add_html_fragment(doc, html, label=DocItemLabel.TEXT)

item = doc.texts[0]
assert isinstance(item, SectionHeaderItem)
assert item.level == depth - 1
assert item.prov[0].page_no == 2
assert doc.export_to_markdown() == f"{'#' * depth} Method"


def test_formatted_html_heading_keeps_one_coordinate_owner() -> None:
doc = parse_chandra_html(
'<div data-bbox="100 100 900 200" data-label="Section-Header">'
"<h3><b>Method</b> and results</h3></div>",
Size(width=600, height=800),
page_no=1,
)
assert isinstance(doc.texts[0], SectionHeaderItem)
assert doc.texts[0].level == 2
assert doc.texts[0].prov
assert all(not item.prov for item in doc.texts[1:])
assert doc.export_to_markdown() == "### **Method** and results"
Loading