From 4de978b919e65d897cfd49eaef0205c9287f0fcc Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Sun, 13 Sep 2026 12:05:20 +0300 Subject: [PATCH] gh-157406: Report all document type declarations in the Python XMLParser (GH-157408) The Python implementation of XMLParser only reported a document type declaration with an external identifier. Use the Expat handler, like the C implementation does. (cherry picked from commit f802d2d246ba9b8ed53aa6e760faad68fd490710) Co-authored-by: Serhiy Storchaka --- Lib/test/test_xml_etree.py | 15 +++++++ Lib/xml/etree/ElementTree.py | 43 +++++-------------- ...-09-13-10-30-00.gh-issue-157406.a3kZq7.rst | 4 ++ 3 files changed, 29 insertions(+), 33 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-09-13-10-30-00.gh-issue-157406.a3kZq7.rst diff --git a/Lib/test/test_xml_etree.py b/Lib/test/test_xml_etree.py index 320ad03e1dc6ee..960b706867a12d 100644 --- a/Lib/test/test_xml_etree.py +++ b/Lib/test/test_xml_etree.py @@ -4002,6 +4002,21 @@ def close(self): ('html', '-//W3C//DTD XHTML 1.0 Transitional//EN', 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd')) + for doctype, expected in [ + ('', ('html', None, None)), + (']>', ('html', None, None)), + ('', ('html', None, 'a.dtd')), + (']>', + ('html', None, 'a.dtd')), + ('', ('html', '-//P', 'a.dtd')), + ("", + ('html', '-//P', 'a.dtd')), + ]: + with self.subTest(doctype=doctype): + parser = ET.XMLParser(target=DoctypeParser()) + parser.feed(doctype + '') + self.assertEqual(parser.close(), expected) + def test_builder_lookup_errors(self): class RaisingBuilder: def __init__(self, raise_in=None, what=ValueError): diff --git a/Lib/xml/etree/ElementTree.py b/Lib/xml/etree/ElementTree.py index 2efaae0b100353..b33005ad7f7dee 100644 --- a/Lib/xml/etree/ElementTree.py +++ b/Lib/xml/etree/ElementTree.py @@ -1572,10 +1572,10 @@ def __init__(self, *, target=None, encoding=None): parser.CommentHandler = target.comment if hasattr(target, 'pi'): parser.ProcessingInstructionHandler = target.pi + parser.StartDoctypeDeclHandler = self._start_doctype # Configure pyexpat: buffering, new-style attribute handling. parser.buffer_text = 1 parser.ordered_attributes = 1 - self._doctype = None self.entity = {} try: self.version = "Expat %d.%d.%d" % expat.version_info @@ -1694,38 +1694,15 @@ def _default(self, text): err.lineno = self.parser.ErrorLineNumber err.offset = self.parser.ErrorColumnNumber raise err - elif prefix == "<" and text[:9] == "": - self._doctype = None - return - text = text.strip(_XML_WHITESPACE) - if not text: - return - self._doctype.append(text) - n = len(self._doctype) - if n > 2: - type = self._doctype[1] - if type == "PUBLIC" and n == 4: - name, type, pubid, system = self._doctype - if pubid: - pubid = pubid[1:-1] - elif type == "SYSTEM" and n == 3: - name, type, system = self._doctype - pubid = None - else: - return - if hasattr(self.target, "doctype"): - self.target.doctype(name, pubid, system[1:-1]) - elif hasattr(self, "doctype"): - warnings.warn( - "The doctype() method of XMLParser is ignored. " - "Define doctype() method on the TreeBuilder target.", - RuntimeWarning) - - self._doctype = None + + def _start_doctype(self, name, system, pubid, has_internal_subset): + if hasattr(self.target, "doctype"): + self.target.doctype(name, pubid, system) + elif hasattr(self, "doctype"): + warnings.warn( + "The doctype() method of XMLParser is ignored. " + "Define doctype() method on the TreeBuilder target.", + RuntimeWarning) def feed(self, data): """Feed encoded data to parser.""" diff --git a/Misc/NEWS.d/next/Library/2026-09-13-10-30-00.gh-issue-157406.a3kZq7.rst b/Misc/NEWS.d/next/Library/2026-09-13-10-30-00.gh-issue-157406.a3kZq7.rst new file mode 100644 index 00000000000000..53f2bf561eefbb --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-09-13-10-30-00.gh-issue-157406.a3kZq7.rst @@ -0,0 +1,4 @@ +Fix the Python implementation of :class:`xml.etree.ElementTree.XMLParser`: +the ``doctype()`` method of the target is now called for a document type +declaration without an external identifier, like ````, +as in the C implementation.