From 6ecfc28c4a7fe7a7e965042f24e1740eab57ed31 Mon Sep 17 00:00:00 2001 From: Yassir Karroum Date: Fri, 26 Apr 2024 14:14:34 +0200 Subject: [PATCH 1/6] bpo-54873: Backported namespaces prefixes support for xml.sax.expatreader from PyXml (0.8.4) --- Lib/test/test_xml_expatreader.py | 24 ++++++++++++++++++++++++ Lib/xml/sax/expatreader.py | 17 ++++++++++------- 2 files changed, 34 insertions(+), 7 deletions(-) create mode 100644 Lib/test/test_xml_expatreader.py diff --git a/Lib/test/test_xml_expatreader.py b/Lib/test/test_xml_expatreader.py new file mode 100644 index 00000000000000..e73e8347fcb411 --- /dev/null +++ b/Lib/test/test_xml_expatreader.py @@ -0,0 +1,24 @@ +import unittest + +from xml.sax.expatreader import create_parser +from xml.sax import handler + + +class feature_namespace_prefixes(unittest.TestCase): + def setUp(self): + self.parser = create_parser() + self.parser.setFeature(handler.feature_namespaces, 1) + self.parser.setFeature(handler.feature_namespace_prefixes, 1) + + def test_prefix_given(self): + class Handler(handler.ContentHandler): + def startElementNS(self, name, qname, attrs): + self.qname = qname + + h = Handler() + + self.parser.setContentHandler(h) + self.parser.feed("") + self.parser.close() + print("self.assertEqual") + self.assertFalse(h.qname is None) diff --git a/Lib/xml/sax/expatreader.py b/Lib/xml/sax/expatreader.py index b9ad52692db8dd..59ded5d12282ce 100644 --- a/Lib/xml/sax/expatreader.py +++ b/Lib/xml/sax/expatreader.py @@ -91,6 +91,7 @@ def __init__(self, namespaceHandling=0, bufsize=2**16-20): self._entity_stack = [] self._external_ges = 0 self._interning = None + self._namespace_prefixes = 1 # XMLReader methods @@ -126,8 +127,9 @@ def getFeature(self, name): return self._namespaces elif name == feature_string_interning: return self._interning is not None - elif name in (feature_validation, feature_external_pes, - feature_namespace_prefixes): + elif name == feature_namespace_prefixes: + return self._namespace_prefixes + elif name in (feature_validation, feature_external_pes): return 0 elif name == feature_external_ges: return self._external_ges @@ -147,6 +149,8 @@ def setFeature(self, name, state): self._interning = {} else: self._interning = None + elif name == feature_namespace_prefixes: + self._namespace_prefixes = state elif name == feature_validation: if state: raise SAXNotSupportedException( @@ -155,10 +159,6 @@ def setFeature(self, name, state): if state: raise SAXNotSupportedException( "expat does not read external parameter entities") - elif name == feature_namespace_prefixes: - if state: - raise SAXNotSupportedException( - "expat does not report namespace prefixes") else: raise SAXNotRecognizedException( "Feature '%s' not recognized" % name) @@ -333,11 +333,14 @@ def start_element_ns(self, name, attrs): pair = name.split() if len(pair) == 1: # no namespace + elem_qname = name pair = (None, name) elif len(pair) == 3: + elem_qname = "%s:%s" % (pair[2], pair[1]) pair = pair[0], pair[1] else: # default namespace + elem_qname = pair[1] pair = tuple(pair) newattrs = {} @@ -360,7 +363,7 @@ def start_element_ns(self, name, attrs): newattrs[apair] = value qnames[apair] = qname - self._cont_handler.startElementNS(pair, None, + self._cont_handler.startElementNS(pair, elem_qname, AttributesNSImpl(newattrs, qnames)) def end_element_ns(self, name): From e9ac4547853070d5ec2eb4935622922d9f99bfd2 Mon Sep 17 00:00:00 2001 From: Yassir Karroum Date: Fri, 26 Apr 2024 14:21:23 +0200 Subject: [PATCH 2/6] bpo-54873: Added News entry --- .../next/Library/2024-04-26-14-21-04.gh-issue-54873.vf2bfp.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2024-04-26-14-21-04.gh-issue-54873.vf2bfp.rst diff --git a/Misc/NEWS.d/next/Library/2024-04-26-14-21-04.gh-issue-54873.vf2bfp.rst b/Misc/NEWS.d/next/Library/2024-04-26-14-21-04.gh-issue-54873.vf2bfp.rst new file mode 100644 index 00000000000000..92c90c19b85dba --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-04-26-14-21-04.gh-issue-54873.vf2bfp.rst @@ -0,0 +1,2 @@ +Backported namespaces prefixes support for xml.sax.expatreader from PyXml +(0.8.4) From be878cbc7bcc8d75b968f60a0c06437b7968c5a9 Mon Sep 17 00:00:00 2001 From: ukarroum17 Date: Sun, 2 Nov 2025 19:52:21 +0000 Subject: [PATCH 3/6] Moved test_namespace_prefix to test_sax.py --- Doc/whatsnew/3.15.rst | 3 ++ Lib/test/test_sax.py | 34 ++++++++++++++++++- Lib/test/test_xml_expatreader.py | 24 ------------- ...4-04-26-14-21-04.gh-issue-54873.vf2bfp.rst | 2 -- 4 files changed, 36 insertions(+), 27 deletions(-) delete mode 100644 Lib/test/test_xml_expatreader.py delete mode 100644 Misc/NEWS.d/next/Library/2024-04-26-14-21-04.gh-issue-54873.vf2bfp.rst diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst index 7338fb51964b8f..044934575b0044 100644 --- a/Doc/whatsnew/3.15.rst +++ b/Doc/whatsnew/3.15.rst @@ -660,6 +660,9 @@ xml.parsers.expat .. _billion laughs: https://en.wikipedia.org/wiki/Billion_laughs_attack +* Add support for namespace prefixes. + (Contributed by Yassir Karroum in :gh:`118317`.) + zlib ---- diff --git a/Lib/test/test_sax.py b/Lib/test/test_sax.py index 5c10bcedc69bc6..0e4365fe22be18 100644 --- a/Lib/test/test_sax.py +++ b/Lib/test/test_sax.py @@ -1,7 +1,7 @@ # regression test for SAX 2.0 from xml.sax import make_parser, ContentHandler, \ - SAXException, SAXReaderNotAvailable, SAXParseException + SAXException, SAXReaderNotAvailable, SAXParseException, handler import unittest from unittest import mock try: @@ -1307,6 +1307,38 @@ def test_expat_locator_withinfo_nonascii(self): self.assertEqual(parser.getSystemId(), fname) self.assertEqual(parser.getPublicId(), None) + def test_namespace_prefix(self): + parser = create_parser() + parser.setFeature(handler.feature_namespaces, 1) + parser.setFeature(handler.feature_namespace_prefixes, 1) + + class Handler(handler.ContentHandler): + def startElementNS(self, name, qname, attrs): + self.qname = qname + + h = Handler() + + parser.setContentHandler(h) + parser.feed("") + parser.close() + self.assertEqual(h.qname, "Q:E") + + def test_default_namespace(self): + parser = create_parser() + parser.setFeature(handler.feature_namespaces, 1) + + class Handler(handler.ContentHandler): + def startElementNS(self, name, qname, attrs): + self.qname = qname + + h = Handler() + + parser.setContentHandler(h) + parser.feed("") + parser.close() + self.assertEqual(h.qname, "E") + + # =========================================================================== # diff --git a/Lib/test/test_xml_expatreader.py b/Lib/test/test_xml_expatreader.py deleted file mode 100644 index e73e8347fcb411..00000000000000 --- a/Lib/test/test_xml_expatreader.py +++ /dev/null @@ -1,24 +0,0 @@ -import unittest - -from xml.sax.expatreader import create_parser -from xml.sax import handler - - -class feature_namespace_prefixes(unittest.TestCase): - def setUp(self): - self.parser = create_parser() - self.parser.setFeature(handler.feature_namespaces, 1) - self.parser.setFeature(handler.feature_namespace_prefixes, 1) - - def test_prefix_given(self): - class Handler(handler.ContentHandler): - def startElementNS(self, name, qname, attrs): - self.qname = qname - - h = Handler() - - self.parser.setContentHandler(h) - self.parser.feed("") - self.parser.close() - print("self.assertEqual") - self.assertFalse(h.qname is None) diff --git a/Misc/NEWS.d/next/Library/2024-04-26-14-21-04.gh-issue-54873.vf2bfp.rst b/Misc/NEWS.d/next/Library/2024-04-26-14-21-04.gh-issue-54873.vf2bfp.rst deleted file mode 100644 index 92c90c19b85dba..00000000000000 --- a/Misc/NEWS.d/next/Library/2024-04-26-14-21-04.gh-issue-54873.vf2bfp.rst +++ /dev/null @@ -1,2 +0,0 @@ -Backported namespaces prefixes support for xml.sax.expatreader from PyXml -(0.8.4) From 0e3f870d6fa31b8508282afe44424230e3355558 Mon Sep 17 00:00:00 2001 From: ukarroum17 Date: Sun, 2 Nov 2025 21:14:53 +0000 Subject: [PATCH 4/6] test_sax: Replaced namespace tests with one test on qualified names --- Lib/test/test_sax.py | 36 ++++++------------- ...4-04-26-14-21-04.gh-issue-54873.vf2bfp.rst | 2 ++ 2 files changed, 12 insertions(+), 26 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-04-26-14-21-04.gh-issue-54873.vf2bfp.rst diff --git a/Lib/test/test_sax.py b/Lib/test/test_sax.py index 0e4365fe22be18..46975e351b42f9 100644 --- a/Lib/test/test_sax.py +++ b/Lib/test/test_sax.py @@ -1,5 +1,4 @@ # regression test for SAX 2.0 - from xml.sax import make_parser, ContentHandler, \ SAXException, SAXReaderNotAvailable, SAXParseException, handler import unittest @@ -1307,37 +1306,22 @@ def test_expat_locator_withinfo_nonascii(self): self.assertEqual(parser.getSystemId(), fname) self.assertEqual(parser.getPublicId(), None) - def test_namespace_prefix(self): - parser = create_parser() - parser.setFeature(handler.feature_namespaces, 1) - parser.setFeature(handler.feature_namespace_prefixes, 1) + def test_qualified_names(self): - class Handler(handler.ContentHandler): + class Handler(ContentHandler): def startElementNS(self, name, qname, attrs): self.qname = qname - h = Handler() - - parser.setContentHandler(h) - parser.feed("") - parser.close() - self.assertEqual(h.qname, "Q:E") - - def test_default_namespace(self): - parser = create_parser() - parser.setFeature(handler.feature_namespaces, 1) + for xml_s, expected_qname in zip(["", "", ""], ["Q:E", "E", "E"]): + parser = create_parser() + parser.setFeature(handler.feature_namespaces, 1) + parser.setFeature(handler.feature_namespace_prefixes, 1) - class Handler(handler.ContentHandler): - def startElementNS(self, name, qname, attrs): - self.qname = qname - - h = Handler() - - parser.setContentHandler(h) - parser.feed("") - parser.close() - self.assertEqual(h.qname, "E") + h = Handler() + parser.setContentHandler(h) + parser.parse(StringIO(xml_s)) + self.assertEqual(h.qname, expected_qname) # =========================================================================== diff --git a/Misc/NEWS.d/next/Library/2024-04-26-14-21-04.gh-issue-54873.vf2bfp.rst b/Misc/NEWS.d/next/Library/2024-04-26-14-21-04.gh-issue-54873.vf2bfp.rst new file mode 100644 index 00000000000000..92c90c19b85dba --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-04-26-14-21-04.gh-issue-54873.vf2bfp.rst @@ -0,0 +1,2 @@ +Backported namespaces prefixes support for xml.sax.expatreader from PyXml +(0.8.4) From c88e7edd2c2749a9243f744d6684f1ab5f8c6bbf Mon Sep 17 00:00:00 2001 From: ukarroum17 Date: Sun, 2 Nov 2025 21:18:15 +0000 Subject: [PATCH 5/6] ExpatParser: Add namespacePrefixesHandling --- Lib/xml/sax/expatreader.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/xml/sax/expatreader.py b/Lib/xml/sax/expatreader.py index 78b79df21ca079..0388f2c5cdc7b9 100644 --- a/Lib/xml/sax/expatreader.py +++ b/Lib/xml/sax/expatreader.py @@ -81,7 +81,7 @@ def getSystemId(self): class ExpatParser(xmlreader.IncrementalParser, xmlreader.Locator): """SAX driver for the pyexpat C module.""" - def __init__(self, namespaceHandling=0, bufsize=2**16-20): + def __init__(self, namespaceHandling=0, namespacePrefixesHandling=0, bufsize=2**16-20): xmlreader.IncrementalParser.__init__(self, bufsize) self._source = xmlreader.InputSource() self._parser = None @@ -91,7 +91,7 @@ def __init__(self, namespaceHandling=0, bufsize=2**16-20): self._entity_stack = [] self._external_ges = 0 self._interning = None - self._namespace_prefixes = 1 + self._namespace_prefixes = namespacePrefixesHandling # XMLReader methods From 27cb2738379943bda5de6609a12073b4507f2dd2 Mon Sep 17 00:00:00 2001 From: ukarroum17 Date: Sun, 2 Nov 2025 21:54:58 +0000 Subject: [PATCH 6/6] namespace prefixes: add link to w3.org --- Doc/whatsnew/3.15.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst index 044934575b0044..9eb09f535cd7a7 100644 --- a/Doc/whatsnew/3.15.rst +++ b/Doc/whatsnew/3.15.rst @@ -660,7 +660,7 @@ xml.parsers.expat .. _billion laughs: https://en.wikipedia.org/wiki/Billion_laughs_attack -* Add support for namespace prefixes. +* Add support for `namespace prefixes `_. (Contributed by Yassir Karroum in :gh:`118317`.)