Count inline code by depth so every span closes

Nested <code><code>x</code></code> opens two spans, but open state was a
flag, so the first end tag cleared it and the guard added last time made
the second a no-op. Output went from four backticks to three and the rest
of the page read as code. The state is a depth now, so each end tag owes
one backtick and the recovered-header guard still holds: a frame closes
only the spans opened inside it.
This commit is contained in:
danielhanchen 2026-07-29 08:55:17 +00:00
commit d9eb4a2f9c
2 changed files with 24 additions and 11 deletions

View file

@ -234,7 +234,7 @@ class _HeaderFrame:
link_seq: int,
cell_seq: int,
in_pre: bool,
in_code: bool,
in_code: int,
bq_depth: int,
list_depth: int,
):
@ -356,7 +356,9 @@ class _MarkdownRenderer(HTMLParser):
# Pre/code state
self._in_pre: bool = False
self._pre_parts: list[str] = []
self._in_inline_code: bool = False
# Depth, not a flag: <code><code>x</code></code> opens two spans and each
# end tag owes a backtick, else the delimiters stop pairing.
self._inline_code_depth: int = 0
# Blockquote state: stack of buffers so nested blockquotes get the right ">" depth.
self._bq_stack: list[list[str]] = []
@ -592,8 +594,8 @@ class _MarkdownRenderer(HTMLParser):
self._finish_link()
# Inline code opened OUTSIDE the header is the page's; closing it here would leave the real
# </code> to emit an unpaired backtick.
if self._in_inline_code and not frame.outer_in_code:
self._in_inline_code = False
while self._inline_code_depth > frame.outer_in_code:
self._inline_code_depth -= 1
self._emit("`")
# Before the cell: _finish_row emits, and an open <pre> would swallow the
# row into the code block as CODE| | instead of a cell holding the code.
@ -658,7 +660,7 @@ class _MarkdownRenderer(HTMLParser):
self._link_seq if self._in_link else -1,
self._cell_seq if self._in_cell else -1,
self._in_pre,
self._in_inline_code,
self._inline_code_depth,
len(self._bq_stack),
len(self._list_stack),
)
@ -793,7 +795,7 @@ class _MarkdownRenderer(HTMLParser):
self._in_pre = True
elif tag == "code" and not self._in_pre:
self._in_inline_code = True
self._inline_code_depth += 1
self._emit("`")
elif tag == "table":
@ -866,8 +868,8 @@ class _MarkdownRenderer(HTMLParser):
# Already closed means a header frame recovered it; a second backtick here would leave the
# rest of the page formatted as code.
elif tag == "code" and not self._in_pre and self._in_inline_code:
self._in_inline_code = False
elif tag == "code" and not self._in_pre and self._inline_code_depth:
self._inline_code_depth -= 1
self._emit("`")
elif tag in ("th", "td"):
@ -900,7 +902,7 @@ class _MarkdownRenderer(HTMLParser):
self._pre_parts.append(data)
return
# Preserve literal whitespace inside inline <code> spans.
if self._in_inline_code:
if self._inline_code_depth:
self._count_header_text(data)
self._emit(data)
return
@ -939,8 +941,8 @@ class _MarkdownRenderer(HTMLParser):
if self._in_link:
self._finish_link()
if self._in_inline_code:
self._in_inline_code = False
while self._inline_code_depth:
self._inline_code_depth -= 1
self._emit("`")
self._finish_cell()

View file

@ -2051,6 +2051,17 @@ def test_header_size_is_independent_of_the_buffer_it_renders_through():
assert len(kept) == 1
def test_nested_inline_code_closes_every_span_it_opened():
# Two <code> elements owe two closing backticks. Tracking open/closed as a
# flag let the first </code> answer for both and left the delimiters odd.
body = "<main><article><p><code><code>x</code></code></p><p>%s</p></article></main>" % (
"Body text here. " * 20,
)
out = html_to_markdown(f"<body>{body}</body>", main_content = True)
assert out.count("`") % 2 == 0
assert "``x``" in out
def test_header_inside_open_inline_code_leaves_delimiters_paired():
# The <code> opened outside the header, so closing it in the frame left </code> unpaired.
body = "<main><code>head<header><h1>T</h1></header>tail</code><p>%s</p></main>" % (