From 14512b65020f00d1f8891b55e688286a15d533d0 Mon Sep 17 00:00:00 2001 From: tonghuaroot Date: Thu, 25 Jun 2026 09:43:51 +0000 Subject: [PATCH] gh-152142: Fix inverted DOMBuilderFilter result for notation declarations xml.dom.expatbuilder.notation_decl_handler dropped a notation when the DOMBuilderFilter accepted it and kept it when the filter rejected it, the inverse of entity_decl_handler and the DOMBuilderFilter contract. Compare against FILTER_REJECT like the other handlers. --- Lib/test/test_minidom.py | 33 +++++++++++++++++++ Lib/xml/dom/expatbuilder.py | 2 +- ...-06-25-09-43-51.gh-issue-152142.cabd13.rst | 4 +++ 3 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2026-06-25-09-43-51.gh-issue-152142.cabd13.rst diff --git a/Lib/test/test_minidom.py b/Lib/test/test_minidom.py index 46249e5138aed52..3aa7e41488ac696 100644 --- a/Lib/test/test_minidom.py +++ b/Lib/test/test_minidom.py @@ -1784,5 +1784,38 @@ def test_cdata_parsing(self): dom2 = parseString(dom1.toprettyxml()) self.checkWholeText(dom2.getElementsByTagName('node')[0].firstChild, '') + def test_notation_decl_filter(self): + # gh-152142: a DOMBuilderFilter accepting a notation must keep it and + # rejecting it must drop it, matching entity declarations. + from xml.dom.expatbuilder import makeBuilder + from xml.dom.xmlbuilder import DOMBuilderFilter, Options + + source = (b'\n' + b'\n' + b' \n' + b']>\n' + b'') + + class Filter(DOMBuilderFilter): + def __init__(self, result): + self.result = result + def acceptNode(self, node): + if node.nodeType == Node.NOTATION_NODE: + return self.result + return self.FILTER_ACCEPT + + def counts(filt): + options = Options() + options.filter = filt + doctype = makeBuilder(options).parseFile(io.BytesIO(source)).doctype + return doctype.notations.length, doctype.entities.length + + # The entity must stay untouched in every case; only the notation + # follows the filter result. + self.assertEqual(counts(None), (1, 1)) + self.assertEqual(counts(Filter(DOMBuilderFilter.FILTER_ACCEPT)), (1, 1)) + self.assertEqual(counts(Filter(DOMBuilderFilter.FILTER_REJECT)), (0, 1)) + if __name__ == "__main__": unittest.main() diff --git a/Lib/xml/dom/expatbuilder.py b/Lib/xml/dom/expatbuilder.py index 7dd667bf3fbe042..c977cc5780c758f 100644 --- a/Lib/xml/dom/expatbuilder.py +++ b/Lib/xml/dom/expatbuilder.py @@ -320,7 +320,7 @@ def entity_decl_handler(self, entityName, is_parameter_entity, value, def notation_decl_handler(self, notationName, base, systemId, publicId): node = self.document._create_notation(notationName, publicId, systemId) self.document.doctype.notations._seq.append(node) - if self._filter and self._filter.acceptNode(node) == FILTER_ACCEPT: + if self._filter and self._filter.acceptNode(node) == FILTER_REJECT: del self.document.doctype.notations._seq[-1] def comment_handler(self, data): diff --git a/Misc/NEWS.d/next/Library/2026-06-25-09-43-51.gh-issue-152142.cabd13.rst b/Misc/NEWS.d/next/Library/2026-06-25-09-43-51.gh-issue-152142.cabd13.rst new file mode 100644 index 000000000000000..a1ead5f1efcd3fa --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-06-25-09-43-51.gh-issue-152142.cabd13.rst @@ -0,0 +1,4 @@ +Fix the handling of notation declarations by +:class:`!xml.dom.xmlbuilder.DOMBuilderFilter` in :mod:`xml.dom.minidom`. A +notation that the filter accepted was dropped and a rejected one was kept, the +opposite of the filter contract and of entity declarations.