#4422·docling

HTML backend: text after a nested list inside <li> is merged into the item and moved before the nested items

Author: morten-lagaboteCreated Sep 28, 2026Updated Sep 28, 2026

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:

xml
<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>:

xml
<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: x

The 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.Popen

Steps to reproduce

python
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