python/cpython · error · AssertionError
unexpected char after internal subset
Error message
unexpected char after internal subset
What it means
AssertionError raised by _markupbase.ParserBase._parse_doctype_subset after the closing ']' of a DOCTYPE internal subset: whitespace is skipped and the next character must be '>'. Any other character at that position means the DOCTYPE declaration is malformed after its subset, and the scanner aborts.
Source
Thrown at Lib/_markupbase.py:237
elif c == "%":
# parameter entity reference
if (j + 1) == n:
# end of buffer; incomplete
return -1
s, j = self._scan_name(j + 1, declstartpos)
if j < 0:
return j
if rawdata[j] == ";":
j = j + 1
elif c == "]":
j = j + 1
while j < n and rawdata[j].isspace():
j = j + 1
if j < n:
if rawdata[j] == ">":
return j
self.updatepos(declstartpos, j)
raise AssertionError("unexpected char after internal subset")
else:
return -1
elif c.isspace():
j = j + 1
else:
self.updatepos(declstartpos, j)
raise AssertionError("unexpected char %r in internal subset" % c)
# end of buffer reached
return -1
# Internal -- scan past <!ELEMENT declarations
def _parse_doctype_element(self, i, declstartpos):
name, j = self._scan_name(i, declstartpos)
if j == -1:
return -1
# style content model; just skip until '>'
rawdata = self.rawdata
if '>' in rawdata[j:]:View on GitHub (pinned to bc6749cc3b)
Solutions
- Ensure the DOCTYPE ends '] >' — i.e. the '>' immediately after optional whitespace following ']'.
- If mangled input is unavoidable, catch AssertionError and skip forward to the next '>' before resuming feed().
- Sanitize with a regex that forces well-formedness: re.sub(r'\]\s*[^>\s][^>]*>', ']>', raw).
- Feed complete declarations per chunk boundary rather than splitting mid-doctype.
Example fix
# before
HTMLParser().feed('<!DOCTYPE d [ <!ENTITY e "x"> ] junk>') # AssertionError after subset
# after
HTMLParser().feed('<!DOCTYPE d [ <!ENTITY e "x"> ]>') Defensive patterns
Strategy: try-catch
Validate before calling
import re
def doctest_end_ok(raw: str) -> bool:
return not re.search(r'\]\s*[^>\s][^>]*>', raw) Try / catch
try:
parser.feed(data)
except AssertionError as e:
if 'after internal subset' in str(e):
data = re.sub(r'\]\s*[^>]*>', ']>', data)
parser.reset(); parser.feed(data)
else:
raise Prevention
- Always terminate DOCTYPE with ']>' after the subset.
- Do not concatenate content directly after a ']'.
- Verify generated doctypes in template unit tests.
When it happens
Trigger: HTMLParser.feed() on <!DOCTYPE d [ ... ] x> — junk between ']' and '>'; also <!DOCTYPE d [ ... ]<?xml ...?> or a truncated buffer where a non-'>' char follows the bracket.
Common situations: Template concatenation placing content right after the subset; regex-based HTML edits that drop the final '>'; chunked streaming where the next chunk's text is glued after ']' without '>' first.
Related errors
- unexpected char in internal subset (in %r)
- unknown declaration %r in internal subset
- unexpected char %r in internal subset
- unsupported '[' char in %s declaration
- unexpected '[' char in declaration
AI-assisted analysis of python/cpython@bc6749cc3b (2026-08-14).
Data as JSON: /api/errors/1fbfe3f5604051fc.
Report an issue: GitHub.