{"record":{"id":"cee5f40755817a62","repo":"python/cpython","slug":"unexpected-char-in-internal-subset-in-r","errorCode":null,"errorMessage":"unexpected char in internal subset (in %r)","messagePattern":"unexpected char in internal subset \\(in %r\\)","errorType":"exception","errorClass":"AssertionError","httpStatus":null,"severity":"error","filePath":"Lib/_markupbase.py","lineNumber":192,"sourceCode":"            self.handle_comment(rawdata[i+4: j])\n        return match.end(0)\n\n    # Internal -- scan past the internal subset in a <!DOCTYPE declaration,\n    # returning the index just past any whitespace following the trailing ']'.\n    def _parse_doctype_subset(self, i, declstartpos):\n        rawdata = self.rawdata\n        n = len(rawdata)\n        j = i\n        while j < n:\n            c = rawdata[j]\n            if c == \"<\":\n                s = rawdata[j:j+2]\n                if s == \"<\":\n                    # end of buffer; incomplete\n                    return -1\n                if s != \"<!\":\n                    self.updatepos(declstartpos, j + 1)\n                    raise AssertionError(\n                        \"unexpected char in internal subset (in %r)\" % s\n                    )\n                if (j + 2) == n:\n                    # end of buffer; incomplete\n                    return -1\n                if (j + 4) > n:\n                    # end of buffer; incomplete\n                    return -1\n                if rawdata[j:j+4] == \"<!--\":\n                    j = self.parse_comment(j, report=0)\n                    if j < 0:\n                        return j\n                    continue\n                name, j = self._scan_name(j + 2, declstartpos)\n                if j == -1:\n                    return -1\n                if name not in {\"attlist\", \"element\", \"entity\", \"notation\"}:\n                    self.updatepos(declstartpos, j + 2)","sourceCodeStart":174,"sourceCodeEnd":210,"githubUrl":"https://github.com/python/cpython/blob/bc6749cc3b5ae4a5e88a6cc2d5b3bebbe354eae6/Lib/_markupbase.py#L174-L210","documentation":"AssertionError raised by _markupbase.ParserBase._parse_doctype_subset while scanning the internal subset of a DOCTYPE declaration: a '<' was found that is not followed by '!'. Inside an internal subset only markup declarations (<!ELEMENT, <!ATTLIST, <!ENTITY, <!NOTATION>, comments, and parameter-entity references) are legal, so anything else is fatal.","triggerScenarios":"HTMLParser.feed() on <!DOCTYPE r [ <element ... > ]> where a declaration is missing its '!', or stray '<' text inside the subset, e.g. <!DOCTYPE d [ < 5 ]>. The two-char slice s = rawdata[j:j+2] is echoed in the message.","commonSituations":"Hand-written DTD internal subsets with typos; HTML documents where a '<' comparison operator appears inside an (invalidly placed) doctype; chunked feeding that leaves a lone '<' at a buffer boundary inside a subset.","solutions":["Fix the source: every '<' inside the internal subset must start a '<!' declaration or '<!--' comment.","Remove the internal subset entirely if unused: replace '\\[.*?\\]' within the DOCTYPE before feeding.","Catch AssertionError, use parser.getpos() to locate the line, and repair just that construct.","For XML with real DTD subsets, switch to an XML parser (xml.etree, lxml) instead of html.parser."],"exampleFix":"# before\nHTMLParser().feed('<!DOCTYPE d [ <ELEMENT a (#PCDATA)> ]>')  # missing '!' -> AssertionError\n\n# after\nHTMLParser().feed('<!DOCTYPE d [ <!ELEMENT a (#PCDATA)> ]>')\n# or drop the subset: HTMLParser().feed('<!DOCTYPE d>')","handlingStrategy":"try-catch","validationCode":"import re\n\ndef internal_subset_ok(raw: str) -> bool:\n    m = re.search(r'<!DOCTYPE[^\\[]*\\[([^\\]]*)\\]', raw, re.I | re.S)\n    if not m:\n        return True\n    subset = m.group(1)\n    return not re.search(r'<(?!!|\\s*$)', subset)  # every '<' must start '<!'","typeGuard":null,"tryCatchPattern":"try:\n    parser.feed(data)\nexcept AssertionError as e:\n    if 'internal subset' in str(e):\n        data = re.sub(r'(<!DOCTYPE[^\\[]*)\\[.*?\\]', r'\\1', data, flags=re.S | re.I)\n        parser.reset(); parser.feed(data)\n    else:\n        raise","preventionTips":["Inside DOCTYPE subsets, only '<!' declarations and '<!--' comments are legal — enforce in templates.","Drop subsets you do not need.","Use an XML parser when DTD subsets matter semantically."],"tags":["markup","html-parser","dtd","doctype","parsing"],"backgroundTag":null,"analyzedSha":"bc6749cc3b5ae4a5e88a6cc2d5b3bebbe354eae6","analyzedAt":"2026-08-14T22:01:13.976Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}