Handle unclosed HTML tags inside a markdown element

This fixes an issue where it was panicking due to an unbalanced HTML tag
when exiting a markdown element. The problem was that the tag stack was
left non-empty when processing was finished due to `end_tag` being out
of sync with the pulldown-cmark event tags.

There really should be better validation that the stack is in sync and
balanced, but this should address the main culprit of the interplay of
raw HTML tags and pulldown-cmark events.
This commit is contained in:
Eric Huss
2025-11-06 07:31:45 -08:00
parent 152132458e
commit 700839f77f
2 changed files with 31 additions and 12 deletions

View File

@@ -583,8 +583,28 @@ where
}
fn end_tag(&mut self, tag: TagEnd) {
// TODO: This should validate that the event stack is
// properly synchronized with the tag stack.
// TODO: This should validate that the event stack is properly
// synchronized with the tag stack. That, would likely require keeping
// a parallel "expected end tag" with the tag stack, since mapping a
// pulldown-cmark event tag to an HTML tag isn't always clear.
//
// Check for unclosed HTML tags when exiting a markdown event.
while let Some(node_id) = self.tag_stack.last() {
let node = self.tree.get(*node_id).unwrap().value();
let Node::Element(el) = node else {
break;
};
if !el.was_raw {
break;
}
warn!(
"unclosed HTML tag `<{}>` found in `{}` while exiting {tag:?}\n\
HTML tags must be closed before exiting a markdown element.",
el.name.local,
self.options.path.display(),
);
self.pop();
}
self.pop();
match tag {
TagEnd::TableHead => {

View File

@@ -290,18 +290,17 @@ fn heading_with_unbalanced_html() {
BookTest::init(|_| {})
.change_file("src/chapter_1.md", "### Option<T>")
.run("build", |cmd| {
cmd.expect_failure().expect_stderr(str![[r#"
cmd.expect_stderr(str![[r#"
INFO Book building has started
INFO Running the html backend
thread 'main' ([..]) panicked at crates/mdbook-html/src/html/tree.rs:[..]
internal error: expected empty tag stack.
path: `chapter_1.md`
element=Element { name: QualName { prefix: None, ns: Atom('http://www.w3.org/1999/xhtml' type=static), local: Atom('h3' type=inline) }, attrs: {}, self_closing: false, was_raw: false }
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
WARN unclosed HTML tag `<t>` found in `chapter_1.md` while exiting Heading(H3)
HTML tags must be closed before exiting a markdown element.
INFO HTML book written to `[ROOT]/book`
"#]]);
});
// .check_main_file("book/chapter_1.html", str![[""]]);
})
.check_main_file(
"book/chapter_1.html",
str![[r##"<h3 id="option"><a class="header" href="#option">Option<t></t></a></h3>"##]],
);
}