Skip to content

Handle orphan li elements as regular blocks#114

Merged
plutext merged 2 commits into
plutext:VERSION_17_0_1from
zyplayer:patch-1
Jul 27, 2026
Merged

Handle orphan li elements as regular blocks#114
plutext merged 2 commits into
plutext:VERSION_17_0_1from
zyplayer:patch-1

Conversation

@zyplayer

Copy link
Copy Markdown

Some input HTML may contain invalid list markup, such as li elements that are not wrapped in an ol or ul. Previously, this could cause list state initialization to fail, interrupting the entire XHTML import flow.

Would it make sense to handle this case more leniently by only applying list numbering logic when the importer is actually inside a list context? With this change, orphan li elements fall back to regular block handling instead of propagating an exception, while valid list items inside ol/ul continue to use the existing numbering logic.

plutext and others added 2 commits June 29, 2026 14:15
Some input HTML may contain invalid list markup, such as li elements that are not wrapped in an ol or ul. Previously, this could cause list state initialization to fail, interrupting the entire XHTML import flow.

Would it make sense to handle this case more leniently by only applying list numbering logic when the importer is actually inside a list context? With this change, orphan li elements fall back to regular block handling instead of propagating an exception, while valid list items inside ol/ul continue to use the existing numbering logic.
@plutext
plutext changed the base branch from VERSION_17_0_0 to VERSION_17_0_1 July 27, 2026 00:24
@plutext
plutext merged commit d7e308e into plutext:VERSION_17_0_1 Jul 27, 2026
@plutext

plutext commented Jul 27, 2026

Copy link
Copy Markdown
Owner

Merged, thanks — this is the right call. Per Claude Opus 5:-

An <li> outside <ol>/<ul> is invalid XHTML, but it's well formed, and well-formedness is the only thing this library actually needs: the input has to parse as XML, but nothing downstream consumes a DTD or schema. openhtmltopdf lays out whatever DOM it's given, driven by CSS, and display: list-item works regardless of ancestry. Browsers render orphan <li> without complaint, and it turns up routinely in CMS output, WYSIWYG editors, sanitizers, and extracted fragments. The importer already accepts fragments that aren't valid documents, so singling out this one invalidity was hard to justify — especially as the old behaviour wasn't a deliberate validity policy, just an NPE with a friendlier message wrapped around it.

I checked the guard before merging: listHelper.getDepth()>0 isn't an approximation of the null check it replaces, it's exactly equivalent — pushListStack/popListStack maintain listStack and listItemStateStack in tandem (ListHelper.java:112-124), so a non-empty listStack is precisely when peekListItemStateStack() is non-null. It's also the idiom already used for "are we in a list" at XHTMLImporterImpl.java:1521, :1591 and :2521, so the change makes that branch consistent with its neighbours.

Verified behaviour across six inputs. Before, all four orphan cases threw; after, all six convert. An orphan <li> yields a plain <w:p> with the text preserved and no numPr, and valid lists are untouched — <ul><li>alpha</li><li>beta</li></ul> still produces numPr with ilvl 0 / numId 1. All existing tests pass.

I've added two things on top:

A log.warn when an <li> is treated as an ordinary block. The conversion now succeeds where it used to fail, but the output silently won't be bulleted or numbered, so anyone upgrading gets a breadcrumb rather than mysteriously different documents.

Tests (OrphanListItemTest) — an orphan in a div, a bare orphan, successive orphans, and an <li> following a closed <ul>. That last one is worth knowing about: the list stack has already popped by then, so it counts as an orphan too. Each asserts the text survives and no numPr is emitted, plus regression tests that valid flat and nested lists are still numbered at the expected ilvl.

One implementation note in case it's useful: the warning needed an AnonymousBlockBox guard. For <li><p>Item 2</p>stray text</li> the anonymous box reaches the same default branch with getElement() returning the li, so without the guard a perfectly valid list item would get warned about.

This will go out in 17.0.1. Note it targeted VERSION_17_0_0; I've merged it to VERSION_17_0_1, which is where current work is landing.

plutext added a commit that referenced this pull request Jul 27, 2026
PR #114 makes an <li> outside ol|ul convert as an ordinary block, rather
than failing.  Such markup is invalid XHTML, but well formed, and browsers
render it, so accepting it is right.  But the result won't be bulleted or
numbered (Word numbering comes from the ol|ul), so warn, since otherwise
the output silently differs from what the author intended.

The AnonymousBlockBox guard matters: for <li><p>x</p>stray text</li> the
anonymous box reaches the same default branch with getElement() returning
the li, so without it a valid list item would be warned about.

Tests cover an orphan in a div, a bare orphan, successive orphans, and an
li following a closed ul (the list stack has popped by then, so it too is
an orphan).  Plus regression tests that valid flat and nested lists are
still numbered, at the expected ilvl.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants