From 622dba6cc5cb6bfa8bcf4239c2cb3cf065f192d2 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Sun, 13 Sep 2026 12:05:20 +0300 Subject: [PATCH] [3.15] 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 620fb1c5aaaa447..e8e2db436a5f8ef 100644 --- a/Lib/test/test_xml_etree.py +++ b/Lib/test/test_xml_etree.py @@ -4036,6 +4036,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 53727d7940b3f2a..1ca2ddaa8e1aaca 100644 --- a/Lib/xml/etree/ElementTree.py +++ b/Lib/xml/etree/ElementTree.py @@ -1583,10 +1583,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 @@ -1705,38 +1705,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() - 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 000000000000000..53f2bf561eefbb7 --- /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.