HTML backend: text after a nested list inside <li> is merged into the item and moved before the nested items
Bug
In the HTML backend, a paragraph that follows a nested list inside the same <li> is merged into that list item's text. The nested list items are emitted after it. Reading order therefore changes: text that the source places after the nested enumeration appears before it, glued to the list item's opening text.
Input:
<ol>
<li>
<p>Intro:</p>
<ol>
<li><p>First nested.</p></li>
<li><p>Second nested.</p></li>
</ol>
<p>After the nested list.</p>
</li>
</ol>
<p>Next paragraph.</p>Actual (label:text in iterate_items() order):
list_item: Intro: After the nested list.
list_item: First nested.
list_item: Second nested.
text: Next paragraph.Expected: After the nested list. comes after the nested items and stays within the outer list item, for example:
list_item: Intro:
list_item: First nested.
list_item: Second nested.
text: After the nested list. (child of the outer list item)
text: Next paragraph.The same happens with text after a nested <table>, and inside <dd>:
<ul><li>Intro<table><tr><td>A</td></tr></table>After the table.</li></ul>
<dl><dt>Term</dt><dd>Intro<ul><li>x</li></ul>After the list.</dd></dl>list_item: Intro After the table.
table
list_item: Term
list_item: Intro After the list.
list_item: xThe list item text is extracted from all the text in the item, skipping nested lists and tables, which are then emitted after it (_add_list_item_with_content and _process_list_item_nested_content).
This pattern is common in documentation generated from reStructuredText or Markdown. For example, https://docs.python.org/3/whatsnew/3.13.html has 25 such list items. In the one for PythonFinalizationError, the attribution after the nested list is moved into the introduction (Markdown export, links removed):
- Add PythonFinalizationError , a new exception derived from RuntimeError and used to signal when operations are blocked during finalization . The following callables now raise PythonFinalizationError , instead of RuntimeError : (Contributed by Victor Stinner in gh-114570 .)
- _thread.start_new_thread()
- os.fork()
- os.forkpty()
- subprocess.PopenSteps to reproduce
from io import BytesIO
from docling.datamodel.base_models import DocumentStream, InputFormat
from docling.document_converter import DocumentConverter
html = """<!DOCTYPE html><html><head><meta charset="utf-8"></head><body>
<ol><li><p>Intro:</p>
<ol><li><p>First nested.</p></li><li><p>Second nested.</p></li></ol>
<p>After the nested list.</p></li></ol>
<p>Next paragraph.</p>
</body></html>"""
converter = DocumentConverter(allowed_formats=[InputFormat.HTML])
document = converter.convert(DocumentStream(name="nested.html", stream=BytesIO(html.encode()))).document
for item, _ in document.iterate_items():
print(f"{item.label.value}: {getattr(item, 'text', '')}")Docling version
2.130.0 (also reproduced with 2.124.0); docling-core 2.99.0; beautifulsoup4 4.15.0
Python version
Python 3.13.9
Source: docling-project/docling