{"record":{"id":"1fbfe3f5604051fc","repo":"python/cpython","slug":"unexpected-char-after-internal-subset","errorCode":null,"errorMessage":"unexpected char after internal subset","messagePattern":"unexpected char after internal subset","errorType":"exception","errorClass":"AssertionError","httpStatus":null,"severity":"error","filePath":"Lib/_markupbase.py","lineNumber":237,"sourceCode":"            elif c == \"%\":\n                # parameter entity reference\n                if (j + 1) == n:\n                    # end of buffer; incomplete\n                    return -1\n                s, j = self._scan_name(j + 1, declstartpos)\n                if j < 0:\n                    return j\n                if rawdata[j] == \";\":\n                    j = j + 1\n            elif c == \"]\":\n                j = j + 1\n                while j < n and rawdata[j].isspace():\n                    j = j + 1\n                if j < n:\n                    if rawdata[j] == \">\":\n                        return j\n                    self.updatepos(declstartpos, j)\n                    raise AssertionError(\"unexpected char after internal subset\")\n                else:\n                    return -1\n            elif c.isspace():\n                j = j + 1\n            else:\n                self.updatepos(declstartpos, j)\n                raise AssertionError(\"unexpected char %r in internal subset\" % c)\n        # end of buffer reached\n        return -1\n\n    # Internal -- scan past <!ELEMENT declarations\n    def _parse_doctype_element(self, i, declstartpos):\n        name, j = self._scan_name(i, declstartpos)\n        if j == -1:\n            return -1\n        # style content model; just skip until '>'\n        rawdata = self.rawdata\n        if '>' in rawdata[j:]:","sourceCodeStart":219,"sourceCodeEnd":255,"githubUrl":"https://github.com/python/cpython/blob/bc6749cc3b5ae4a5e88a6cc2d5b3bebbe354eae6/Lib/_markupbase.py#L219-L255","documentation":"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.","triggerScenarios":"HTMLParser.feed() on <!DOCTYPE d [ ... ] x> — junk between ']' and '>'; also <!DOCTYPE d [ ... ]<?xml ...?> or a truncated buffer where a non-'>' char follows the bracket.","commonSituations":"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.","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."],"exampleFix":"# before\nHTMLParser().feed('<!DOCTYPE d [ <!ENTITY e \"x\"> ] junk>')  # AssertionError after subset\n\n# after\nHTMLParser().feed('<!DOCTYPE d [ <!ENTITY e \"x\"> ]>')","handlingStrategy":"try-catch","validationCode":"import re\n\ndef doctest_end_ok(raw: str) -> bool:\n    return not re.search(r'\\]\\s*[^>\\s][^>]*>', raw)","typeGuard":null,"tryCatchPattern":"try:\n    parser.feed(data)\nexcept AssertionError as e:\n    if 'after internal subset' in str(e):\n        data = re.sub(r'\\]\\s*[^>]*>', ']>', data)\n        parser.reset(); parser.feed(data)\n    else:\n        raise","preventionTips":["Always terminate DOCTYPE with ']>' after the subset.","Do not concatenate content directly after a ']'.","Verify generated doctypes in template unit tests."],"tags":["markup","html-parser","doctype","parsing"],"backgroundTag":null,"analyzedSha":"bc6749cc3b5ae4a5e88a6cc2d5b3bebbe354eae6","analyzedAt":"2026-08-14T22:01:13.976Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}