From aceee9a8a26b4180db25528f7cd8074e07abcc9f Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Tue, 1 Sep 2026 10:27:41 +0300 Subject: [PATCH] gh-63102: Support custom targets in the pull parser The list of events was collected by the TreeBuilder in the C implementation, so the pull parser only worked with the standard target. The parser itself now collects the events, and reports what the target returns. XMLPullParser and iterparse() get the target parameter, which makes it possible to parse a large document incrementally without building a tree for it. The namespace events no longer need a separate code path: the parser reports the prefix and the uri if the target does not implement start_ns()/end_ns(), as the Python implementation already did. --- Doc/library/xml.etree.elementtree.rst | 42 ++- Doc/whatsnew/3.16.rst | 7 + Lib/test/test_xml_etree.py | 89 ++++++ Lib/xml/etree/ElementTree.py | 16 +- ...6-09-01-02-30-00.gh-issue-63102.Pz3wK8.rst | 6 + Modules/_elementtree.c | 285 +++++++----------- 6 files changed, 262 insertions(+), 183 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-09-01-02-30-00.gh-issue-63102.Pz3wK8.rst diff --git a/Doc/library/xml.etree.elementtree.rst b/Doc/library/xml.etree.elementtree.rst index ec272a076c902bf..923ad1060c6dd1f 100644 --- a/Doc/library/xml.etree.elementtree.rst +++ b/Doc/library/xml.etree.elementtree.rst @@ -611,10 +611,11 @@ Functions element instance. Return ``True`` if this is an element object. -.. function:: iterparse(source, events=None, parser=None) +.. function:: iterparse(source, events=None, parser=None, *, target=None) - Parses an XML section into an element tree incrementally, and reports what's - going on to the user. *source* is a filename or :term:`file object` + Parses an XML section incrementally, and reports what's going on to the + user. Unless a custom target is used, an element tree is built. + *source* is a filename or :term:`file object` containing XML data. *events* is a sequence of events to report back. The supported events are the strings ``"start"``, ``"end"``, ``"comment"``, ``"pi"``, ``"start-ns"`` and ``"end-ns"`` @@ -622,11 +623,16 @@ Functions information). If *events* is omitted, only ``"end"`` events are reported. *parser* is an optional parser instance. If not given, the standard :class:`XMLParser` parser is used. - *parser* must be an instance of :class:`XMLParser` or its subclass - and can only use the default :class:`TreeBuilder` as a target. - Returns an :term:`iterator` providing ``(event, elem)`` pairs; + *parser* must be an instance of :class:`XMLParser` or its subclass. + *target* is the target of the standard parser; + it cannot be used together with *parser*. + Returns an :term:`iterator` providing ``(event, obj)`` pairs, + as described for :meth:`XMLPullParser.read_events`; it has a ``root`` attribute that references the root element of the resulting XML tree once *source* is fully read. + If a custom target is used, it is set to the value returned + by the :meth:`!close` method of the target. + The iterator has the :meth:`!close` method that closes the internal file object if *source* is a filename. @@ -658,6 +664,9 @@ Functions A :exc:`ResourceWarning` is now emitted if the iterator opened a file and is not explicitly closed. + .. versionchanged:: next + Added the *target* parameter. + .. function:: parse(source, parser=None) @@ -1491,7 +1500,7 @@ XMLParser Objects XMLPullParser Objects ^^^^^^^^^^^^^^^^^^^^^ -.. class:: XMLPullParser(events=None) +.. class:: XMLPullParser(events=None, *, target=None) A pull parser suitable for non-blocking applications. Its input-side API is similar to that of :class:`XMLParser`, but instead of pushing calls to a @@ -1502,6 +1511,18 @@ XMLPullParser Objects are used to get detailed namespace information). If *events* is omitted, only ``"end"`` events are reported. + *target* is the target object of the underlying :class:`XMLParser`. + If omitted, the standard :class:`TreeBuilder` is used, + and the reported objects are :class:`Element` instances. + With other targets the reported object is the value returned + by the corresponding method of the target, + so no tree is built if the target does not build one. + The ``"start-ns"`` and ``"end-ns"`` events are reported as before + if the target does not implement :meth:`!start_ns` and :meth:`!end_ns`. + + .. versionchanged:: next + Added the *target* parameter. + .. method:: feed(data) Feed the given data to the parser. *data* is a string @@ -1534,9 +1555,10 @@ XMLPullParser Objects Return an iterator over the events which have been encountered in the data fed to the - parser. The iterator yields ``(event, elem)`` pairs, where *event* is a - string representing the type of event (e.g. ``"end"``) and *elem* is the - encountered :class:`Element` object, or other context value as follows. + parser. The iterator yields ``(event, obj)`` pairs, where *event* is a + string representing the type of event (e.g. ``"end"``) and *obj* is the + object returned by the corresponding method of the target. + With the standard :class:`TreeBuilder` it is as follows. * ``start``, ``end``: the current Element. * ``comment``, ``pi``: the current comment / processing instruction diff --git a/Doc/whatsnew/3.16.rst b/Doc/whatsnew/3.16.rst index 3262acd87d6d49f..f99cb93b1a1c1dd 100644 --- a/Doc/whatsnew/3.16.rst +++ b/Doc/whatsnew/3.16.rst @@ -660,6 +660,13 @@ xml rather than defaulted from the DTD. (Contributed by Jason Orendorff and Serhiy Storchaka in :gh:`44871`.) +* :class:`~xml.etree.ElementTree.XMLPullParser` and + :func:`~xml.etree.ElementTree.iterparse` now support the *target* parameter. + The reported object is the value returned by the corresponding method of + the target, so a large document can be parsed incrementally without + building a tree for it. + (Contributed by Serhiy Storchaka in :gh:`63102`.) + zipfile ------- diff --git a/Lib/test/test_xml_etree.py b/Lib/test/test_xml_etree.py index f9ff8c4c3541eda..ba734103fcd6bb0 100644 --- a/Lib/test/test_xml_etree.py +++ b/Lib/test/test_xml_etree.py @@ -1656,6 +1656,43 @@ def test_unknown_events(self): del cm gc_collect() + class Target: + # a target which does not build a tree + def start(self, tag, attrib): + return tag + def end(self, tag): + return tag + def data(self, data): + pass + + def test_target(self): + # gh-63102: a custom target reports its own objects + with open(SIMPLE_XMLFILE, 'rb') as f: + it = ET.iterparse(f, events=('start', 'end'), target=self.Target()) + self.assertEqual(list(it), [ + ('start', 'root'), + ('start', 'element'), + ('end', 'element'), + ('start', 'element'), + ('end', 'element'), + ('start', 'empty-element'), + ('end', 'empty-element'), + ('end', 'root'), + ]) + self.assertIsNone(it.root) + + def test_parser_with_target(self): + with open(SIMPLE_XMLFILE, 'rb') as f: + parser = ET.XMLParser(target=self.Target()) + it = ET.iterparse(f, events=('start',), parser=parser) + self.assertEqual(next(it), ('start', 'root')) + + def test_target_and_parser(self): + with self.assertRaisesRegex(ValueError, + "can't specify both parser and target"): + ET.iterparse(SIMPLE_XMLFILE, parser=ET.XMLParser(), + target=self.Target()) + def test_non_utf8(self): source = io.BytesIO( b"\n" @@ -2067,6 +2104,58 @@ def __next__(self): self._feed(parser, "bar") self.assert_event_tags(parser, [('start', 'foo'), ('end', 'foo')]) + # gh-63102: the pull parser reports events from any target + class SimpleTarget: + def start(self, tag, attrib): + return ('start', tag) + def end(self, tag): + return ('end', tag) + def data(self, data): + pass + def comment(self, text): + return ('comment', text) + def pi(self, target, data=None): + return ('pi', target) + def close(self): + return 'closed' + + def test_custom_target(self): + parser = ET.XMLPullParser(events=('start', 'end'), + target=self.SimpleTarget()) + self._feed(parser, "") + self.assert_event_tuples(parser, [ + ('start', ('start', 'root')), + ('start', ('start', 'element')), + ('end', ('end', 'element')), + ('end', ('end', 'root')), + ]) + + def test_custom_target_comment_pi(self): + parser = ET.XMLPullParser(events=('comment', 'pi'), + target=self.SimpleTarget()) + self._feed(parser, "") + self.assert_event_tuples(parser, [ + ('comment', ('comment', ' text ')), + ('pi', ('pi', 'pitarget')), + ]) + + def test_custom_target_ns_events(self): + # the target does not implement start_ns()/end_ns(), + # so the prefix and the uri are reported + parser = ET.XMLPullParser(events=('start-ns', 'end-ns'), + target=self.SimpleTarget()) + self._feed(parser, "") + self.assert_event_tuples(parser, [ + ('start-ns', ('', 'namespace')), + ('end-ns', None), + ]) + + def test_custom_target_close(self): + parser = ET.XMLPullParser(events=('end',), target=self.SimpleTarget()) + self._feed(parser, "") + parser.close() + self.assert_event_tuples(parser, [('end', ('end', 'root'))]) + def test_unknown_event(self): with self.assertRaises(ValueError): ET.XMLPullParser(events=('start', 'end', 'bogus')) diff --git a/Lib/xml/etree/ElementTree.py b/Lib/xml/etree/ElementTree.py index bed8c27df5a3845..a49a6a4cbcd7391 100644 --- a/Lib/xml/etree/ElementTree.py +++ b/Lib/xml/etree/ElementTree.py @@ -1239,7 +1239,7 @@ def parse(source, parser=None): return tree -def iterparse(source, events=None, parser=None): +def iterparse(source, events=None, parser=None, *, target=None): """Incrementally parse XML document into ElementTree. This class also reports what's going on to the user based on the @@ -1250,14 +1250,14 @@ def iterparse(source, events=None, parser=None): *source* is a filename or file object containing XML data, *events* is a list of events to report back, *parser* is an optional parser - instance. + instance, *target* is an optional target of the standard parser. Returns an iterator providing (event, elem) pairs. """ # Use the internal, undocumented _parser argument for now; When the # parser argument of iterparse is removed, this can be killed. - pullparser = XMLPullParser(events=events, _parser=parser) + pullparser = XMLPullParser(events=events, target=target, _parser=parser) if not hasattr(source, "read"): source = open(source, "rb") @@ -1309,13 +1309,19 @@ def __del__(self, _warn=warnings.warn): class XMLPullParser: - def __init__(self, events=None, *, _parser=None): + def __init__(self, events=None, *, target=None, _parser=None): # The _parser argument is for internal use only and must not be relied # upon in user code. It will be removed in a future release. # See https://bugs.python.org/issue17741 for more details. self._events_queue = collections.deque() - self._parser = _parser or XMLParser(target=TreeBuilder()) + if _parser is None: + if target is None: + target = TreeBuilder() + _parser = XMLParser(target=target) + elif target is not None: + raise ValueError("can't specify both parser and target") + self._parser = _parser # wire up the parser for event reporting if events is None: events = ("end",) diff --git a/Misc/NEWS.d/next/Library/2026-09-01-02-30-00.gh-issue-63102.Pz3wK8.rst b/Misc/NEWS.d/next/Library/2026-09-01-02-30-00.gh-issue-63102.Pz3wK8.rst new file mode 100644 index 000000000000000..cd769c804aab6c6 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-09-01-02-30-00.gh-issue-63102.Pz3wK8.rst @@ -0,0 +1,6 @@ +:class:`~xml.etree.ElementTree.XMLPullParser` and +:func:`~xml.etree.ElementTree.iterparse` now support the *target* parameter. +The reported object is the value returned by the corresponding method +of the target, so no tree is built if the target does not build one. +Only the standard :class:`~xml.etree.ElementTree.TreeBuilder` was supported +in the C implementation before. diff --git a/Modules/_elementtree.c b/Modules/_elementtree.c index 18bbbb618c2b18b..7dbca9292493341 100644 --- a/Modules/_elementtree.c +++ b/Modules/_elementtree.c @@ -2409,14 +2409,6 @@ typedef struct { PyObject *pi_factory; /* element tracing */ - PyObject *events_append; /* the append method of the list of events, or NULL */ - PyObject *start_event_obj; /* event objects (NULL to ignore) */ - PyObject *end_event_obj; - PyObject *start_ns_event_obj; - PyObject *end_ns_event_obj; - PyObject *comment_event_obj; - PyObject *pi_event_obj; - char insert_comments; char insert_pis; elementtreestate *state; @@ -2450,10 +2442,6 @@ treebuilder_new(PyTypeObject *type, PyObject *args, PyObject *kwds) } t->index = 0; - t->events_append = NULL; - t->start_event_obj = t->end_event_obj = NULL; - t->start_ns_event_obj = t->end_ns_event_obj = NULL; - t->comment_event_obj = t->pi_event_obj = NULL; t->insert_comments = t->insert_pis = 0; t->state = get_elementtree_state_by_type(type); } @@ -2518,13 +2506,6 @@ treebuilder_gc_traverse(PyObject *op, visitproc visit, void *arg) { TreeBuilderObject *self = _TreeBuilder_CAST(op); Py_VISIT(Py_TYPE(self)); - Py_VISIT(self->pi_event_obj); - Py_VISIT(self->comment_event_obj); - Py_VISIT(self->end_ns_event_obj); - Py_VISIT(self->start_ns_event_obj); - Py_VISIT(self->end_event_obj); - Py_VISIT(self->start_event_obj); - Py_VISIT(self->events_append); Py_VISIT(self->root); Py_VISIT(self->this); Py_VISIT(self->last); @@ -2541,13 +2522,6 @@ static int treebuilder_gc_clear(PyObject *op) { TreeBuilderObject *self = _TreeBuilder_CAST(op); - Py_CLEAR(self->pi_event_obj); - Py_CLEAR(self->comment_event_obj); - Py_CLEAR(self->end_ns_event_obj); - Py_CLEAR(self->start_ns_event_obj); - Py_CLEAR(self->end_event_obj); - Py_CLEAR(self->start_event_obj); - Py_CLEAR(self->events_append); Py_CLEAR(self->stack); Py_CLEAR(self->data); Py_CLEAR(self->last); @@ -2717,24 +2691,6 @@ treebuilder_add_subelement(elementtreestate *st, PyObject *element, } } -LOCAL(int) -treebuilder_append_event(TreeBuilderObject *self, PyObject *action, - PyObject *node) -{ - if (action != NULL) { - PyObject *res; - PyObject *event = _PyTuple_FromPair(action, node); - if (event == NULL) - return -1; - res = PyObject_CallOneArg(self->events_append, event); - Py_DECREF(event); - if (res == NULL) - return -1; - Py_DECREF(res); - } - return 0; -} - /* -------------------------------------------------------------------- */ /* handlers */ @@ -2800,9 +2756,6 @@ treebuilder_handle_start(TreeBuilderObject* self, PyObject* tag, Py_SETREF(self->this, Py_NewRef(node)); Py_SETREF(self->last, Py_NewRef(node)); - if (treebuilder_append_event(self, self->start_event_obj, node) < 0) - goto error; - return node; error: @@ -2873,11 +2826,6 @@ treebuilder_handle_end(TreeBuilderObject* self, PyObject* tag) Py_DECREF(last); Py_XDECREF(last_for_tail); - if (treebuilder_append_event(self, self->end_event_obj, self->last) < 0) { - Py_DECREF(this); - return NULL; - } - return this; } @@ -2907,11 +2855,6 @@ treebuilder_handle_comment(TreeBuilderObject* self, PyObject* text) comment = Py_NewRef(text); } - if (self->events_append && self->comment_event_obj) { - if (treebuilder_append_event(self, self->comment_event_obj, comment) < 0) - goto error; - } - return comment; error: @@ -2950,11 +2893,6 @@ treebuilder_handle_pi(TreeBuilderObject* self, PyObject* target, PyObject* text) } } - if (self->events_append && self->pi_event_obj) { - if (treebuilder_append_event(self, self->pi_event_obj, pi) < 0) - goto error; - } - return pi; error: @@ -2962,39 +2900,6 @@ treebuilder_handle_pi(TreeBuilderObject* self, PyObject* target, PyObject* text) return NULL; } -LOCAL(PyObject*) -treebuilder_handle_start_ns(TreeBuilderObject* self, PyObject* prefix, PyObject* uri) -{ - PyObject* parcel; - - if (self->events_append && self->start_ns_event_obj) { - parcel = _PyTuple_FromPair(prefix, uri); - if (!parcel) { - return NULL; - } - - if (treebuilder_append_event(self, self->start_ns_event_obj, parcel) < 0) { - Py_DECREF(parcel); - return NULL; - } - Py_DECREF(parcel); - } - - Py_RETURN_NONE; -} - -LOCAL(PyObject*) -treebuilder_handle_end_ns(TreeBuilderObject* self, PyObject* prefix) -{ - if (self->events_append && self->end_ns_event_obj) { - if (treebuilder_append_event(self, self->end_ns_event_obj, prefix) < 0) { - return NULL; - } - } - - Py_RETURN_NONE; -} - /* -------------------------------------------------------------------- */ /* methods (in alphabetical order) */ @@ -3125,6 +3030,15 @@ typedef struct { PyObject *handle_start_ns; PyObject *handle_end_ns; + + /* event reporting for the pull API */ + PyObject *events_append; /* the append method of the list of events */ + PyObject *start_event_obj; /* event objects (NULL to ignore) */ + PyObject *end_event_obj; + PyObject *start_ns_event_obj; + PyObject *end_ns_event_obj; + PyObject *comment_event_obj; + PyObject *pi_event_obj; PyObject *handle_start; PyObject *handle_data; PyObject *handle_end; @@ -3314,6 +3228,26 @@ expat_default_handler(void *op, const XML_Char *data_in, int data_len) Py_DECREF(key); } +/* Append (action, node) to the list of events of the pull parser. */ +LOCAL(int) +xmlparser_append_event(XMLParserObject *self, PyObject *action, PyObject *node) +{ + if (self->events_append == NULL || action == NULL || node == NULL) { + return 0; + } + PyObject *event = _PyTuple_FromPair(action, node); + if (event == NULL) { + return -1; + } + PyObject *res = PyObject_CallOneArg(self->events_append, event); + Py_DECREF(event); + if (res == NULL) { + return -1; + } + Py_DECREF(res); + return 0; +} + static void expat_start_handler(void *op, const XML_Char *tag_in, const XML_Char **attrib_in) @@ -3389,7 +3323,10 @@ expat_start_handler(void *op, const XML_Char *tag_in, Py_DECREF(tag); Py_XDECREF(attrib); - Py_XDECREF(res); + if (res != NULL) { + (void)xmlparser_append_event(self, self->start_event_obj, res); + Py_DECREF(res); + } } static void @@ -3446,7 +3383,10 @@ expat_end_handler(void *op, const XML_Char *tag_in) } } - Py_XDECREF(res); + if (res != NULL) { + (void)xmlparser_append_event(self, self->end_event_obj, res); + Py_DECREF(res); + } } static void @@ -3466,42 +3406,34 @@ expat_start_ns_handler(void *op, const XML_Char *prefix_in, if (!prefix_in) prefix_in = ""; - elementtreestate *st = self->state; - if (TreeBuilder_CheckExact(st, self->target)) { - /* shortcut - TreeBuilder does not actually implement .start_ns() */ - TreeBuilderObject *target = (TreeBuilderObject*) self->target; - - if (target->events_append && target->start_ns_event_obj) { - prefix = PyUnicode_DecodeUTF8(prefix_in, strlen(prefix_in), "strict"); - if (!prefix) - return; - uri = PyUnicode_DecodeUTF8(uri_in, strlen(uri_in), "strict"); - if (!uri) { - Py_DECREF(prefix); - return; - } + if (self->handle_start_ns == NULL && self->start_ns_event_obj == NULL) { + return; + } - res = treebuilder_handle_start_ns(target, prefix, uri); - Py_DECREF(uri); - Py_DECREF(prefix); - } - } else if (self->handle_start_ns) { - prefix = PyUnicode_DecodeUTF8(prefix_in, strlen(prefix_in), "strict"); - if (!prefix) - return; - uri = PyUnicode_DecodeUTF8(uri_in, strlen(uri_in), "strict"); - if (!uri) { - Py_DECREF(prefix); - return; - } + prefix = PyUnicode_DecodeUTF8(prefix_in, strlen(prefix_in), "strict"); + if (!prefix) + return; + uri = PyUnicode_DecodeUTF8(uri_in, strlen(uri_in), "strict"); + if (!uri) { + Py_DECREF(prefix); + return; + } + if (self->handle_start_ns) { PyObject *args[2] = {prefix, uri}; res = PyObject_Vectorcall(self->handle_start_ns, args, 2, NULL); - Py_DECREF(uri); - Py_DECREF(prefix); } + else { + /* the target does not implement .start_ns(), report the pair */ + res = _PyTuple_FromPair(prefix, uri); + } + Py_DECREF(uri); + Py_DECREF(prefix); - Py_XDECREF(res); + if (res != NULL) { + (void)xmlparser_append_event(self, self->start_ns_event_obj, res); + Py_DECREF(res); + } } static void @@ -3517,15 +3449,7 @@ expat_end_ns_handler(void *op, const XML_Char *prefix_in) if (!prefix_in) prefix_in = ""; - elementtreestate *st = self->state; - if (TreeBuilder_CheckExact(st, self->target)) { - /* shortcut - TreeBuilder does not actually implement .end_ns() */ - TreeBuilderObject *target = (TreeBuilderObject*) self->target; - - if (target->events_append && target->end_ns_event_obj) { - res = treebuilder_handle_end_ns(target, Py_None); - } - } else if (self->handle_end_ns) { + if (self->handle_end_ns) { prefix = PyUnicode_DecodeUTF8(prefix_in, strlen(prefix_in), "strict"); if (!prefix) return; @@ -3533,8 +3457,15 @@ expat_end_ns_handler(void *op, const XML_Char *prefix_in) res = PyObject_CallOneArg(self->handle_end_ns, prefix); Py_DECREF(prefix); } + else if (self->end_ns_event_obj) { + /* the target does not implement .end_ns() */ + res = Py_NewRef(Py_None); + } - Py_XDECREF(res); + if (res != NULL) { + (void)xmlparser_append_event(self, self->end_ns_event_obj, res); + Py_DECREF(res); + } } static void @@ -3557,16 +3488,22 @@ expat_comment_handler(void *op, const XML_Char *comment_in) return; /* parser will look for errors */ res = treebuilder_handle_comment(target, comment); - Py_XDECREF(res); Py_DECREF(comment); + if (res != NULL) { + (void)xmlparser_append_event(self, self->comment_event_obj, res); + Py_DECREF(res); + } } else if (self->handle_comment) { comment = PyUnicode_DecodeUTF8(comment_in, strlen(comment_in), "strict"); if (!comment) return; res = PyObject_CallOneArg(self->handle_comment, comment); - Py_XDECREF(res); Py_DECREF(comment); + if (res != NULL) { + (void)xmlparser_append_event(self, self->comment_event_obj, res); + Py_DECREF(res); + } } } @@ -3646,7 +3583,7 @@ expat_pi_handler(void *op, const XML_Char *target_in, /* shortcut */ TreeBuilderObject *target = (TreeBuilderObject*) self->target; - if ((target->events_append && target->pi_event_obj) || target->insert_pis) { + if (self->pi_event_obj || target->insert_pis) { pi_target = PyUnicode_DecodeUTF8(target_in, strlen(target_in), "strict"); if (!pi_target) goto error; @@ -3654,9 +3591,12 @@ expat_pi_handler(void *op, const XML_Char *target_in, if (!data) goto error; res = treebuilder_handle_pi(target, pi_target, data); - Py_XDECREF(res); Py_DECREF(data); Py_DECREF(pi_target); + if (res != NULL) { + (void)xmlparser_append_event(self, self->pi_event_obj, res); + Py_DECREF(res); + } } } else if (self->handle_pi) { pi_target = PyUnicode_DecodeUTF8(target_in, strlen(target_in), "strict"); @@ -3668,9 +3608,12 @@ expat_pi_handler(void *op, const XML_Char *target_in, PyObject *args[2] = {pi_target, data}; res = PyObject_Vectorcall(self->handle_pi, args, 2, NULL); - Py_XDECREF(res); Py_DECREF(data); Py_DECREF(pi_target); + if (res != NULL) { + (void)xmlparser_append_event(self, self->pi_event_obj, res); + Py_DECREF(res); + } } return; @@ -3693,6 +3636,10 @@ xmlparser_new(PyTypeObject *type, PyObject *args, PyObject *kwds) self->handle_start = self->handle_data = self->handle_end = NULL; self->handle_comment = self->handle_pi = self->handle_close = NULL; self->handle_doctype = NULL; + self->events_append = NULL; + self->start_event_obj = self->end_event_obj = NULL; + self->start_ns_event_obj = self->end_ns_event_obj = NULL; + self->comment_event_obj = self->pi_event_obj = NULL; self->elementtree_module = PyType_GetModuleByDef(type, &elementtreemodule); assert(self->elementtree_module != NULL); Py_INCREF(self->elementtree_module); @@ -3861,6 +3808,13 @@ xmlparser_gc_traverse(PyObject *op, visitproc visit, void *arg) Py_VISIT(self->handle_start_ns); Py_VISIT(self->handle_end_ns); Py_VISIT(self->handle_doctype); + Py_VISIT(self->events_append); + Py_VISIT(self->start_event_obj); + Py_VISIT(self->end_event_obj); + Py_VISIT(self->start_ns_event_obj); + Py_VISIT(self->end_ns_event_obj); + Py_VISIT(self->comment_event_obj); + Py_VISIT(self->pi_event_obj); Py_VISIT(self->target); Py_VISIT(self->entity); @@ -3890,6 +3844,13 @@ xmlparser_gc_clear(PyObject *op) Py_CLEAR(self->handle_start_ns); Py_CLEAR(self->handle_end_ns); Py_CLEAR(self->handle_doctype); + Py_CLEAR(self->events_append); + Py_CLEAR(self->start_event_obj); + Py_CLEAR(self->end_event_obj); + Py_CLEAR(self->start_ns_event_obj); + Py_CLEAR(self->end_ns_event_obj); + Py_CLEAR(self->comment_event_obj); + Py_CLEAR(self->pi_event_obj); Py_CLEAR(self->target); Py_CLEAR(self->entity); @@ -4181,40 +4142,28 @@ _elementtree_XMLParser__setevents_impl(XMLParserObject *self, { /* activate element event reporting */ Py_ssize_t i; - TreeBuilderObject *target; PyObject *events_append, *events_seq; if (!_check_xmlparser(self)) { return NULL; } elementtreestate *st = self->state; - if (!TreeBuilder_CheckExact(st, self->target)) { - PyErr_SetString( - PyExc_TypeError, - "event handling only supported for ElementTree.TreeBuilder " - "targets" - ); - return NULL; - } - - target = (TreeBuilderObject*) self->target; - events_append = PyObject_GetAttrString(events_queue, "append"); if (events_append == NULL) return NULL; - Py_XSETREF(target->events_append, events_append); + Py_XSETREF(self->events_append, events_append); /* clear out existing events */ - Py_CLEAR(target->start_event_obj); - Py_CLEAR(target->end_event_obj); - Py_CLEAR(target->start_ns_event_obj); - Py_CLEAR(target->end_ns_event_obj); - Py_CLEAR(target->comment_event_obj); - Py_CLEAR(target->pi_event_obj); + Py_CLEAR(self->start_event_obj); + Py_CLEAR(self->end_event_obj); + Py_CLEAR(self->start_ns_event_obj); + Py_CLEAR(self->end_ns_event_obj); + Py_CLEAR(self->comment_event_obj); + Py_CLEAR(self->pi_event_obj); if (events_to_report == Py_None) { /* default is "end" only */ - target->end_event_obj = PyUnicode_FromString("end"); + self->end_event_obj = PyUnicode_FromString("end"); Py_RETURN_NONE; } @@ -4238,31 +4187,31 @@ _elementtree_XMLParser__setevents_impl(XMLParserObject *self, } if (strcmp(event_name, "start") == 0) { - Py_XSETREF(target->start_event_obj, Py_NewRef(event_name_obj)); + Py_XSETREF(self->start_event_obj, Py_NewRef(event_name_obj)); } else if (strcmp(event_name, "end") == 0) { - Py_XSETREF(target->end_event_obj, Py_NewRef(event_name_obj)); + Py_XSETREF(self->end_event_obj, Py_NewRef(event_name_obj)); } else if (strcmp(event_name, "start-ns") == 0) { - Py_XSETREF(target->start_ns_event_obj, Py_NewRef(event_name_obj)); + Py_XSETREF(self->start_ns_event_obj, Py_NewRef(event_name_obj)); EXPAT(st, SetNamespaceDeclHandler)( self->parser, (XML_StartNamespaceDeclHandler) expat_start_ns_handler, (XML_EndNamespaceDeclHandler) expat_end_ns_handler ); } else if (strcmp(event_name, "end-ns") == 0) { - Py_XSETREF(target->end_ns_event_obj, Py_NewRef(event_name_obj)); + Py_XSETREF(self->end_ns_event_obj, Py_NewRef(event_name_obj)); EXPAT(st, SetNamespaceDeclHandler)( self->parser, (XML_StartNamespaceDeclHandler) expat_start_ns_handler, (XML_EndNamespaceDeclHandler) expat_end_ns_handler ); } else if (strcmp(event_name, "comment") == 0) { - Py_XSETREF(target->comment_event_obj, Py_NewRef(event_name_obj)); + Py_XSETREF(self->comment_event_obj, Py_NewRef(event_name_obj)); EXPAT(st, SetCommentHandler)( self->parser, (XML_CommentHandler) expat_comment_handler ); } else if (strcmp(event_name, "pi") == 0) { - Py_XSETREF(target->pi_event_obj, Py_NewRef(event_name_obj)); + Py_XSETREF(self->pi_event_obj, Py_NewRef(event_name_obj)); EXPAT(st, SetProcessingInstructionHandler)( self->parser, (XML_ProcessingInstructionHandler) expat_pi_handler