https://github.com/python/cpython/commit/f802d2d246ba9b8ed53aa6e760faad68fd490710
commit: f802d2d246ba9b8ed53aa6e760faad68fd490710
branch: main
author: Serhiy Storchaka <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2026-09-13T09:05:20Z
summary:
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.
files:
A Misc/NEWS.d/next/Library/2026-09-13-10-30-00.gh-issue-157406.a3kZq7.rst
M Lib/test/test_xml_etree.py
M Lib/xml/etree/ElementTree.py
diff --git a/Lib/test/test_xml_etree.py b/Lib/test/test_xml_etree.py
index f9ff8c4c3541eda..90e556ec95308bc 100644
--- a/Lib/test/test_xml_etree.py
+++ b/Lib/test/test_xml_etree.py
@@ -4211,6 +4211,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 [
+ ('<!DOCTYPE html>', ('html', None, None)),
+ ('<!DOCTYPE html [<!ENTITY e "v">]>', ('html', None, None)),
+ ('<!DOCTYPE html SYSTEM "a.dtd">', ('html', None, 'a.dtd')),
+ ('<!DOCTYPE html SYSTEM "a.dtd" [<!ENTITY e "v">]>',
+ ('html', None, 'a.dtd')),
+ ('<!DOCTYPE html PUBLIC "-//P" "a.dtd">', ('html', '-//P',
'a.dtd')),
+ ("<!DOCTYPE\nhtml\nPUBLIC\n'-//P'\n'a.dtd'\n>",
+ ('html', '-//P', 'a.dtd')),
+ ]:
+ with self.subTest(doctype=doctype):
+ parser = ET.XMLParser(target=DoctypeParser())
+ parser.feed(doctype + '<html/>')
+ 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 bed8c27df5a3845..ce98e4dc24a0d3a 100644
--- a/Lib/xml/etree/ElementTree.py
+++ b/Lib/xml/etree/ElementTree.py
@@ -1591,10 +1591,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
@@ -1713,38 +1713,15 @@ def _default(self, text):
err.lineno = self.parser.ErrorLineNumber
err.offset = self.parser.ErrorColumnNumber
raise err
- elif prefix == "<" and text[:9] == "<!DOCTYPE":
- self._doctype = [] # inside a doctype declaration
- elif self._doctype is not None:
- # parse doctype contents
- if prefix == ">":
- 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 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 ``<!DOCTYPE html>``,
+as in the C implementation.
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]