| /*-------------------------------------------------------------------- | |
| * Licensed to PSF under a Contributor Agreement. | |
| * See https://www.python.org/psf/license for licensing details. | |
| * | |
| * _elementtree - C accelerator for xml.etree.ElementTree | |
| * Copyright (c) 1999-2009 by Secret Labs AB. All rights reserved. | |
| * Copyright (c) 1999-2009 by Fredrik Lundh. | |
| * | |
| * info@pythonware.com | |
| * http://www.pythonware.com | |
| *-------------------------------------------------------------------- | |
| */ | |
| #ifndef Py_BUILD_CORE_BUILTIN | |
| # define Py_BUILD_CORE_MODULE 1 | |
| #endif | |
| #include "Python.h" | |
| #include "pycore_ceval.h" // _Py_EnterRecursiveCall() | |
| #include "pycore_dict.h" // _PyDict_CopyAsDict() | |
| #include "pycore_pyhash.h" // _Py_HashSecret | |
| #include "pycore_tuple.h" // _PyTuple_FromPair | |
| #include "pycore_weakref.h" // FT_CLEAR_WEAKREFS() | |
| #include <stddef.h> // offsetof() | |
| #include "expat.h" | |
| #include "pyexpat.h" | |
| /* -------------------------------------------------------------------- */ | |
| /* configuration */ | |
| /* An element can hold this many children without extra memory | |
| allocations. */ | |
| #define STATIC_CHILDREN 4 | |
| /* For best performance, chose a value so that 80-90% of all nodes | |
| have no more than the given number of children. Set this to zero | |
| to minimize the size of the element structure itself (this only | |
| helps if you have lots of leaf nodes with attributes). */ | |
| /* Also note that pymalloc always allocates blocks in multiples of | |
| eight bytes. For the current C version of ElementTree, this means | |
| that the number of children should be an even number, at least on | |
| 32-bit platforms. */ | |
| /* -------------------------------------------------------------------- */ | |
| /* compiler tweaks */ | |
| #if defined(_MSC_VER) | |
| #define LOCAL(type) static __inline type __fastcall | |
| #else | |
| #define LOCAL(type) static type | |
| #endif | |
| /* macros used to store 'join' flags in string object pointers. note | |
| that all use of text and tail as object pointers must be wrapped in | |
| JOIN_OBJ. see comments in the ElementObject definition for more | |
| info. */ | |
| #define JOIN_GET(p) ((uintptr_t) (p) & 1) | |
| #define JOIN_SET(p, flag) ((void*) ((uintptr_t) (JOIN_OBJ(p)) | (flag))) | |
| #define JOIN_OBJ(p) ((PyObject*) ((uintptr_t) (p) & ~(uintptr_t)1)) | |
| /* Py_SETREF for a PyObject* that uses a join flag. */ | |
| Py_LOCAL_INLINE(void) | |
| _set_joined_ptr(PyObject **p, PyObject *new_joined_ptr) | |
| { | |
| PyObject *tmp = JOIN_OBJ(*p); | |
| *p = new_joined_ptr; | |
| Py_DECREF(tmp); | |
| } | |
| /* Py_CLEAR for a PyObject* that uses a join flag. Pass the pointer by | |
| * reference since this function sets it to NULL. | |
| */ | |
| static void _clear_joined_ptr(PyObject **p) | |
| { | |
| if (*p) { | |
| _set_joined_ptr(p, NULL); | |
| } | |
| } | |
| /* Per-module state; PEP 3121 */ | |
| typedef struct { | |
| PyObject *parseerror_obj; | |
| PyObject *deepcopy_obj; | |
| PyObject *elementpath_obj; | |
| PyObject *comment_factory; | |
| PyObject *pi_factory; | |
| /* Interned strings */ | |
| PyObject *str_text; | |
| PyObject *str_tail; | |
| PyObject *str_append; | |
| PyObject *str_find; | |
| PyObject *str_findtext; | |
| PyObject *str_findall; | |
| PyObject *str_iterfind; | |
| PyObject *str_doctype; | |
| /* Types defined by this extension */ | |
| PyTypeObject *Element_Type; | |
| PyTypeObject *ElementIter_Type; | |
| PyTypeObject *TreeBuilder_Type; | |
| PyTypeObject *XMLParser_Type; | |
| PyObject *expat_capsule; | |
| struct PyExpat_CAPI *expat_capi; | |
| } elementtreestate; | |
| static struct PyModuleDef elementtreemodule; | |
| /* Given a module object (assumed to be _elementtree), get its per-module | |
| * state. | |
| */ | |
| static inline elementtreestate* | |
| get_elementtree_state(PyObject *module) | |
| { | |
| void *state = PyModule_GetState(module); | |
| assert(state != NULL); | |
| return (elementtreestate *)state; | |
| } | |
| static inline elementtreestate * | |
| get_elementtree_state_by_cls(PyTypeObject *cls) | |
| { | |
| void *state = PyType_GetModuleState(cls); | |
| assert(state != NULL); | |
| return (elementtreestate *)state; | |
| } | |
| static inline elementtreestate * | |
| get_elementtree_state_by_type(PyTypeObject *tp) | |
| { | |
| PyObject *mod = PyType_GetModuleByDef(tp, &elementtreemodule); | |
| assert(mod != NULL); | |
| return get_elementtree_state(mod); | |
| } | |
| static int | |
| elementtree_clear(PyObject *m) | |
| { | |
| elementtreestate *st = get_elementtree_state(m); | |
| Py_CLEAR(st->parseerror_obj); | |
| Py_CLEAR(st->deepcopy_obj); | |
| Py_CLEAR(st->elementpath_obj); | |
| Py_CLEAR(st->comment_factory); | |
| Py_CLEAR(st->pi_factory); | |
| // Interned strings | |
| Py_CLEAR(st->str_append); | |
| Py_CLEAR(st->str_find); | |
| Py_CLEAR(st->str_findall); | |
| Py_CLEAR(st->str_findtext); | |
| Py_CLEAR(st->str_iterfind); | |
| Py_CLEAR(st->str_tail); | |
| Py_CLEAR(st->str_text); | |
| Py_CLEAR(st->str_doctype); | |
| // Heap types | |
| Py_CLEAR(st->Element_Type); | |
| Py_CLEAR(st->ElementIter_Type); | |
| Py_CLEAR(st->TreeBuilder_Type); | |
| Py_CLEAR(st->XMLParser_Type); | |
| Py_CLEAR(st->expat_capsule); | |
| st->expat_capi = NULL; | |
| return 0; | |
| } | |
| static int | |
| elementtree_traverse(PyObject *m, visitproc visit, void *arg) | |
| { | |
| elementtreestate *st = get_elementtree_state(m); | |
| Py_VISIT(st->parseerror_obj); | |
| Py_VISIT(st->deepcopy_obj); | |
| Py_VISIT(st->elementpath_obj); | |
| Py_VISIT(st->comment_factory); | |
| Py_VISIT(st->pi_factory); | |
| // Heap types | |
| Py_VISIT(st->Element_Type); | |
| Py_VISIT(st->ElementIter_Type); | |
| Py_VISIT(st->TreeBuilder_Type); | |
| Py_VISIT(st->XMLParser_Type); | |
| Py_VISIT(st->expat_capsule); | |
| return 0; | |
| } | |
| static void | |
| elementtree_free(void *m) | |
| { | |
| (void)elementtree_clear((PyObject *)m); | |
| } | |
| /* helpers */ | |
| LOCAL(PyObject*) | |
| list_join(PyObject* list) | |
| { | |
| /* join list elements */ | |
| PyObject* joiner; | |
| PyObject* result; | |
| joiner = Py_GetConstant(Py_CONSTANT_EMPTY_STR); | |
| if (!joiner) | |
| return NULL; | |
| result = PyUnicode_Join(joiner, list); | |
| Py_DECREF(joiner); | |
| return result; | |
| } | |
| /* Is the given object an empty dictionary? | |
| */ | |
| static int | |
| is_empty_dict(PyObject *obj) | |
| { | |
| return PyDict_CheckExact(obj) && PyDict_GET_SIZE(obj) == 0; | |
| } | |
| /* -------------------------------------------------------------------- */ | |
| /* the Element type */ | |
| typedef struct { | |
| /* attributes (a dictionary object), or NULL if no attributes */ | |
| PyObject* attrib; | |
| /* child elements */ | |
| Py_ssize_t length; /* actual number of items */ | |
| Py_ssize_t allocated; /* allocated items */ | |
| /* this either points to _children or to a malloced buffer */ | |
| PyObject* *children; | |
| PyObject* _children[STATIC_CHILDREN]; | |
| } ElementObjectExtra; | |
| typedef struct { | |
| PyObject_HEAD | |
| /* element tag (a string). */ | |
| PyObject* tag; | |
| /* text before first child. note that this is a tagged pointer; | |
| use JOIN_OBJ to get the object pointer. the join flag is used | |
| to distinguish lists created by the tree builder from lists | |
| assigned to the attribute by application code; the former | |
| should be joined before being returned to the user, the latter | |
| should be left intact. */ | |
| PyObject* text; | |
| /* text after this element, in parent. note that this is a tagged | |
| pointer; use JOIN_OBJ to get the object pointer. */ | |
| PyObject* tail; | |
| ElementObjectExtra* extra; | |
| PyObject *weakreflist; /* For tp_weaklistoffset */ | |
| } ElementObject; | |
| #define _Element_CAST(op) ((ElementObject *)(op)) | |
| #define Element_CheckExact(st, op) Py_IS_TYPE(op, (st)->Element_Type) | |
| #define Element_Check(st, op) PyObject_TypeCheck(op, (st)->Element_Type) | |
| /* -------------------------------------------------------------------- */ | |
| /* Element constructors and destructor */ | |
| LOCAL(int) | |
| create_extra(ElementObject* self, PyObject* attrib) | |
| { | |
| self->extra = PyMem_Malloc(sizeof(ElementObjectExtra)); | |
| if (!self->extra) { | |
| PyErr_NoMemory(); | |
| return -1; | |
| } | |
| self->extra->attrib = Py_XNewRef(attrib); | |
| self->extra->length = 0; | |
| self->extra->allocated = STATIC_CHILDREN; | |
| self->extra->children = self->extra->_children; | |
| return 0; | |
| } | |
| LOCAL(void) | |
| dealloc_extra(ElementObjectExtra *extra) | |
| { | |
| Py_ssize_t i; | |
| if (!extra) | |
| return; | |
| Py_XDECREF(extra->attrib); | |
| for (i = 0; i < extra->length; i++) | |
| Py_DECREF(extra->children[i]); | |
| if (extra->children != extra->_children) { | |
| PyMem_Free(extra->children); | |
| } | |
| PyMem_Free(extra); | |
| } | |
| LOCAL(void) | |
| clear_extra(ElementObject* self) | |
| { | |
| ElementObjectExtra *myextra; | |
| if (!self->extra) | |
| return; | |
| /* Avoid DECREFs calling into this code again (cycles, etc.) | |
| */ | |
| myextra = self->extra; | |
| self->extra = NULL; | |
| dealloc_extra(myextra); | |
| } | |
| /* Convenience internal function to create new Element objects with the given | |
| * tag and attributes. | |
| */ | |
| LOCAL(PyObject*) | |
| create_new_element(elementtreestate *st, PyObject *tag, PyObject *attrib) | |
| { | |
| ElementObject* self; | |
| self = PyObject_GC_New(ElementObject, st->Element_Type); | |
| if (self == NULL) | |
| return NULL; | |
| self->extra = NULL; | |
| self->tag = Py_NewRef(tag); | |
| self->text = Py_NewRef(Py_None); | |
| self->tail = Py_NewRef(Py_None); | |
| self->weakreflist = NULL; | |
| PyObject_GC_Track(self); | |
| if (attrib != NULL && !is_empty_dict(attrib)) { | |
| if (create_extra(self, attrib) < 0) { | |
| Py_DECREF(self); | |
| return NULL; | |
| } | |
| } | |
| return (PyObject*) self; | |
| } | |
| static PyObject * | |
| element_new(PyTypeObject *type, PyObject *args, PyObject *kwds) | |
| { | |
| ElementObject *e = (ElementObject *)type->tp_alloc(type, 0); | |
| if (e != NULL) { | |
| e->tag = Py_NewRef(Py_None); | |
| e->text = Py_NewRef(Py_None); | |
| e->tail = Py_NewRef(Py_None); | |
| e->extra = NULL; | |
| e->weakreflist = NULL; | |
| } | |
| return (PyObject *)e; | |
| } | |
| /* Helper function for extracting the attrib dictionary from a keywords dict. | |
| * This is required by some constructors/functions in this module that can | |
| * either accept attrib as a keyword argument or all attributes splashed | |
| * directly into *kwds. | |
| * | |
| * Return a dictionary with the content of kwds merged into the content of | |
| * attrib. If there is no attrib keyword, return a copy of kwds. | |
| */ | |
| static PyObject* | |
| get_attrib_from_keywords(PyObject *kwds) | |
| { | |
| PyObject *attrib; | |
| if (PyDict_PopString(kwds, "attrib", &attrib) < 0) { | |
| return NULL; | |
| } | |
| if (attrib) { | |
| /* If attrib was found in kwds, copy its value and remove it from | |
| * kwds | |
| */ | |
| if (!PyAnyDict_Check(attrib)) { | |
| PyErr_Format(PyExc_TypeError, | |
| "attrib must be dict or frozendict, not %T", | |
| attrib); | |
| Py_DECREF(attrib); | |
| return NULL; | |
| } | |
| Py_SETREF(attrib, _PyDict_CopyAsDict(attrib)); | |
| } | |
| else { | |
| attrib = PyDict_New(); | |
| } | |
| if (attrib != NULL && PyDict_Update(attrib, kwds) < 0) { | |
| Py_DECREF(attrib); | |
| return NULL; | |
| } | |
| return attrib; | |
| } | |
| /*[clinic input] | |
| module _elementtree | |
| class _elementtree.Element "ElementObject *" "clinic_state()->Element_Type" | |
| class _elementtree.TreeBuilder "TreeBuilderObject *" "clinic_state()->TreeBuilder_Type" | |
| class _elementtree.XMLParser "XMLParserObject *" "clinic_state()->XMLParser_Type" | |
| [clinic start generated code]*/ | |
| /*[clinic end generated code: output=da39a3ee5e6b4b0d input=6c83ea832d2b0ef1]*/ | |
| static int | |
| element_init(PyObject *self, PyObject *args, PyObject *kwds) | |
| { | |
| PyObject *tag; | |
| PyObject *attrib = NULL; | |
| ElementObject *self_elem; | |
| if (!PyArg_ParseTuple(args, "O|O:Element", &tag, &attrib)) | |
| return -1; | |
| if (attrib != NULL && !PyAnyDict_Check(attrib)) { | |
| PyErr_Format(PyExc_TypeError, | |
| "Element() argument 2 must be dict or frozendict, not %T", | |
| attrib); | |
| return -1; | |
| } | |
| if (attrib) { | |
| /* attrib passed as positional arg */ | |
| attrib = _PyDict_CopyAsDict(attrib); | |
| if (!attrib) | |
| return -1; | |
| if (kwds) { | |
| if (PyDict_Update(attrib, kwds) < 0) { | |
| Py_DECREF(attrib); | |
| return -1; | |
| } | |
| } | |
| } else if (kwds) { | |
| /* have keywords args */ | |
| attrib = get_attrib_from_keywords(kwds); | |
| if (!attrib) | |
| return -1; | |
| } | |
| self_elem = (ElementObject *)self; | |
| if (attrib != NULL && !is_empty_dict(attrib)) { | |
| if (create_extra(self_elem, attrib) < 0) { | |
| Py_DECREF(attrib); | |
| return -1; | |
| } | |
| } | |
| /* We own a reference to attrib here and it's no longer needed. */ | |
| Py_XDECREF(attrib); | |
| /* Replace the objects already pointed to by tag, text and tail. */ | |
| Py_XSETREF(self_elem->tag, Py_NewRef(tag)); | |
| _set_joined_ptr(&self_elem->text, Py_NewRef(Py_None)); | |
| _set_joined_ptr(&self_elem->tail, Py_NewRef(Py_None)); | |
| return 0; | |
| } | |
| LOCAL(int) | |
| element_resize(ElementObject* self, Py_ssize_t extra) | |
| { | |
| Py_ssize_t size; | |
| PyObject* *children; | |
| assert(extra >= 0); | |
| /* make sure self->children can hold the given number of extra | |
| elements. set an exception and return -1 if allocation failed */ | |
| if (!self->extra) { | |
| if (create_extra(self, NULL) < 0) | |
| return -1; | |
| } | |
| size = self->extra->length + extra; /* never overflows */ | |
| if (size > self->extra->allocated) { | |
| /* use Python 2.4's list growth strategy */ | |
| size = (size >> 3) + (size < 9 ? 3 : 6) + size; | |
| /* Coverity CID #182 size_error: Allocating 1 bytes to pointer "children" | |
| * which needs at least 4 bytes. | |
| * Although it's a false alarm always assume at least one child to | |
| * be safe. | |
| */ | |
| size = size ? size : 1; | |
| if ((size_t)size > PY_SSIZE_T_MAX/sizeof(PyObject*)) | |
| goto nomemory; | |
| if (self->extra->children != self->extra->_children) { | |
| /* Coverity CID #182 size_error: Allocating 1 bytes to pointer | |
| * "children", which needs at least 4 bytes. Although it's a | |
| * false alarm always assume at least one child to be safe. | |
| */ | |
| children = PyMem_Realloc(self->extra->children, | |
| size * sizeof(PyObject*)); | |
| if (!children) { | |
| goto nomemory; | |
| } | |
| } else { | |
| children = PyMem_Malloc(size * sizeof(PyObject*)); | |
| if (!children) { | |
| goto nomemory; | |
| } | |
| /* copy existing children from static area to malloc buffer */ | |
| memcpy(children, self->extra->children, | |
| self->extra->length * sizeof(PyObject*)); | |
| } | |
| self->extra->children = children; | |
| self->extra->allocated = size; | |
| } | |
| return 0; | |
| nomemory: | |
| PyErr_NoMemory(); | |
| return -1; | |
| } | |
| LOCAL(void) | |
| raise_type_error(PyObject *element) | |
| { | |
| PyErr_Format(PyExc_TypeError, | |
| "expected an Element, not \"%.200s\"", | |
| Py_TYPE(element)->tp_name); | |
| } | |
| LOCAL(int) | |
| element_add_subelement(elementtreestate *st, ElementObject *self, | |
| PyObject *element) | |
| { | |
| /* add a child element to a parent */ | |
| if (!Element_Check(st, element)) { | |
| raise_type_error(element); | |
| return -1; | |
| } | |
| if (element_resize(self, 1) < 0) | |
| return -1; | |
| self->extra->children[self->extra->length] = Py_NewRef(element); | |
| self->extra->length++; | |
| return 0; | |
| } | |
| LOCAL(PyObject*) | |
| element_get_attrib(ElementObject* self) | |
| { | |
| /* return borrowed reference to attrib dictionary */ | |
| /* note: this function assumes that the extra section exists */ | |
| PyObject* res = self->extra->attrib; | |
| if (!res) { | |
| /* create missing dictionary */ | |
| res = self->extra->attrib = PyDict_New(); | |
| } | |
| return res; | |
| } | |
| LOCAL(PyObject*) | |
| element_get_text(ElementObject* self) | |
| { | |
| /* return new reference to text attribute */ | |
| PyObject *res = self->text; | |
| if (JOIN_GET(res)) { | |
| res = JOIN_OBJ(res); | |
| if (PyList_CheckExact(res)) { | |
| PyObject *tmp = list_join(res); | |
| if (!tmp) | |
| return NULL; | |
| self->text = tmp; | |
| Py_SETREF(res, tmp); | |
| } | |
| } | |
| return Py_NewRef(res); | |
| } | |
| LOCAL(PyObject*) | |
| element_get_tail(ElementObject* self) | |
| { | |
| /* return new reference to tail attribute */ | |
| PyObject *res = self->tail; | |
| if (JOIN_GET(res)) { | |
| res = JOIN_OBJ(res); | |
| if (PyList_CheckExact(res)) { | |
| PyObject *tmp = list_join(res); | |
| if (!tmp) | |
| return NULL; | |
| self->tail = tmp; | |
| Py_SETREF(res, tmp); | |
| } | |
| } | |
| return Py_NewRef(res); | |
| } | |
| /*[clinic input] | |
| _elementtree.SubElement | |
| parent: object(subclass_of='get_elementtree_state(module)->Element_Type') | |
| tag: object | |
| attrib: object(subclass_of='&PyDict_Type', c_default='NULL') = {} | |
| / | |
| **extra: dict | |
| Create a new subelement of the parent element. | |
| [clinic start generated code]*/ | |
| static PyObject * | |
| _elementtree_SubElement_impl(PyObject *module, PyObject *parent, | |
| PyObject *tag, PyObject *attrib, | |
| PyObject *extra) | |
| /*[clinic end generated code: output=42e8a4ebc5db08aa input=8588fc68283cdaa0]*/ | |
| { | |
| PyObject* elem; | |
| elementtreestate *st = get_elementtree_state(module); | |
| ElementObject* parent_elem = (ElementObject *)parent; | |
| if (attrib) { | |
| /* attrib passed as positional arg */ | |
| attrib = PyDict_Copy(attrib); | |
| if (!attrib) | |
| return NULL; | |
| if (PyDict_Update(attrib, extra) < 0) { | |
| Py_DECREF(attrib); | |
| return NULL; | |
| } | |
| } else if (PyDict_GET_SIZE(extra)) { | |
| /* have keyword args */ | |
| attrib = get_attrib_from_keywords(extra); | |
| if (!attrib) | |
| return NULL; | |
| } else { | |
| /* no attrib arg, no kwds, so no attribute */ | |
| } | |
| elem = create_new_element(st, tag, attrib); | |
| Py_XDECREF(attrib); | |
| if (elem == NULL) | |
| return NULL; | |
| if (element_add_subelement(st, parent_elem, elem) < 0) { | |
| Py_DECREF(elem); | |
| return NULL; | |
| } | |
| return elem; | |
| } | |
| static int | |
| element_gc_traverse(PyObject *op, visitproc visit, void *arg) | |
| { | |
| ElementObject *self = _Element_CAST(op); | |
| Py_VISIT(Py_TYPE(self)); | |
| Py_VISIT(self->tag); | |
| Py_VISIT(JOIN_OBJ(self->text)); | |
| Py_VISIT(JOIN_OBJ(self->tail)); | |
| if (self->extra) { | |
| Py_ssize_t i; | |
| Py_VISIT(self->extra->attrib); | |
| for (i = 0; i < self->extra->length; ++i) | |
| Py_VISIT(self->extra->children[i]); | |
| } | |
| return 0; | |
| } | |
| static int | |
| element_gc_clear(PyObject *op) | |
| { | |
| ElementObject *self = _Element_CAST(op); | |
| Py_CLEAR(self->tag); | |
| _clear_joined_ptr(&self->text); | |
| _clear_joined_ptr(&self->tail); | |
| /* After dropping all references from extra, it's no longer valid anyway, | |
| * so fully deallocate it. | |
| */ | |
| clear_extra(self); | |
| return 0; | |
| } | |
| static void | |
| element_dealloc(PyObject *op) | |
| { | |
| ElementObject *self = _Element_CAST(op); | |
| PyTypeObject *tp = Py_TYPE(self); | |
| /* bpo-31095: UnTrack is needed before calling any callbacks */ | |
| PyObject_GC_UnTrack(self); | |
| FT_CLEAR_WEAKREFS(op, self->weakreflist); | |
| /* element_gc_clear clears all references and deallocates extra | |
| */ | |
| (void)element_gc_clear(op); | |
| tp->tp_free(self); | |
| Py_DECREF(tp); | |
| } | |
| /* -------------------------------------------------------------------- */ | |
| /*[clinic input] | |
| _elementtree.Element.append | |
| cls: defining_class | |
| subelement: object(subclass_of='clinic_state()->Element_Type') | |
| / | |
| Add *subelement* to the end of this element. | |
| The new element will appear in document order after the last | |
| existing subelement (or directly after the text, if it's the first | |
| subelement), but before the end tag for this element. | |
| [clinic start generated code]*/ | |
| static PyObject * | |
| _elementtree_Element_append_impl(ElementObject *self, PyTypeObject *cls, | |
| PyObject *subelement) | |
| /*[clinic end generated code: output=d00923711ea317fc input=a59ebce98937a372]*/ | |
| { | |
| elementtreestate *st = get_elementtree_state_by_cls(cls); | |
| if (element_add_subelement(st, self, subelement) < 0) | |
| return NULL; | |
| Py_RETURN_NONE; | |
| } | |
| /*[clinic input] | |
| _elementtree.Element.clear | |
| Reset element. | |
| This function removes all subelements, clears all attributes, and | |
| sets the text and tail attributes to None. | |
| [clinic start generated code]*/ | |
| static PyObject * | |
| _elementtree_Element_clear_impl(ElementObject *self) | |
| /*[clinic end generated code: output=8bcd7a51f94cfff6 input=135c2ab634d0fdf5]*/ | |
| { | |
| clear_extra(self); | |
| _set_joined_ptr(&self->text, Py_NewRef(Py_None)); | |
| _set_joined_ptr(&self->tail, Py_NewRef(Py_None)); | |
| Py_RETURN_NONE; | |
| } | |
| /*[clinic input] | |
| _elementtree.Element.__copy__ | |
| cls: defining_class | |
| / | |
| [clinic start generated code]*/ | |
| static PyObject * | |
| _elementtree_Element___copy___impl(ElementObject *self, PyTypeObject *cls) | |
| /*[clinic end generated code: output=da22894421ff2b36 input=91edb92d9f441213]*/ | |
| { | |
| Py_ssize_t i; | |
| ElementObject* element; | |
| elementtreestate *st = get_elementtree_state_by_cls(cls); | |
| element = (ElementObject*) create_new_element( | |
| st, self->tag, self->extra ? self->extra->attrib : NULL); | |
| if (!element) | |
| return NULL; | |
| Py_INCREF(JOIN_OBJ(self->text)); | |
| _set_joined_ptr(&element->text, self->text); | |
| Py_INCREF(JOIN_OBJ(self->tail)); | |
| _set_joined_ptr(&element->tail, self->tail); | |
| assert(!element->extra || !element->extra->length); | |
| if (self->extra) { | |
| if (element_resize(element, self->extra->length) < 0) { | |
| Py_DECREF(element); | |
| return NULL; | |
| } | |
| for (i = 0; i < self->extra->length; i++) { | |
| element->extra->children[i] = Py_NewRef(self->extra->children[i]); | |
| } | |
| assert(!element->extra->length); | |
| element->extra->length = self->extra->length; | |
| } | |
| return (PyObject*) element; | |
| } | |
| /* Helper for a deep copy. */ | |
| LOCAL(PyObject *) deepcopy(elementtreestate *, PyObject *, PyObject *); | |
| /*[clinic input] | |
| _elementtree.Element.__deepcopy__ | |
| memo: object(subclass_of="&PyDict_Type") | |
| / | |
| [clinic start generated code]*/ | |
| static PyObject * | |
| _elementtree_Element___deepcopy___impl(ElementObject *self, PyObject *memo) | |
| /*[clinic end generated code: output=eefc3df50465b642 input=a2d40348c0aade10]*/ | |
| { | |
| Py_ssize_t i; | |
| ElementObject* element = NULL; | |
| PyObject* tag; | |
| PyObject* attrib; | |
| PyObject* text; | |
| PyObject* tail; | |
| PyObject* id; | |
| if (_Py_EnterRecursiveCall(" in Element.__deepcopy__")) { | |
| return NULL; | |
| } | |
| PyTypeObject *tp = Py_TYPE(self); | |
| elementtreestate *st = get_elementtree_state_by_type(tp); | |
| // The deepcopy() helper takes care of incrementing the refcount | |
| // of the object to copy so to avoid use-after-frees. | |
| tag = deepcopy(st, self->tag, memo); | |
| if (!tag) { | |
| goto error; | |
| } | |
| if (self->extra && self->extra->attrib) { | |
| attrib = deepcopy(st, self->extra->attrib, memo); | |
| if (!attrib) { | |
| Py_DECREF(tag); | |
| goto error; | |
| } | |
| } else { | |
| attrib = NULL; | |
| } | |
| element = (ElementObject*) create_new_element(st, tag, attrib); | |
| Py_DECREF(tag); | |
| Py_XDECREF(attrib); | |
| if (!element) { | |
| goto error; | |
| } | |
| text = deepcopy(st, JOIN_OBJ(self->text), memo); | |
| if (!text) | |
| goto error; | |
| _set_joined_ptr(&element->text, JOIN_SET(text, JOIN_GET(self->text))); | |
| tail = deepcopy(st, JOIN_OBJ(self->tail), memo); | |
| if (!tail) | |
| goto error; | |
| _set_joined_ptr(&element->tail, JOIN_SET(tail, JOIN_GET(self->tail))); | |
| assert(!element->extra || !element->extra->length); | |
| if (self->extra) { | |
| Py_ssize_t expected_count = self->extra->length; | |
| if (element_resize(element, expected_count) < 0) { | |
| assert(!element->extra->length); | |
| goto error; | |
| } | |
| for (i = 0; self->extra && i < self->extra->length; i++) { | |
| PyObject* child = deepcopy(st, self->extra->children[i], memo); | |
| if (!child || !Element_Check(st, child)) { | |
| if (child) { | |
| raise_type_error(child); | |
| Py_DECREF(child); | |
| } | |
| element->extra->length = i; | |
| goto error; | |
| } | |
| if (self->extra && expected_count != self->extra->length) { | |
| // 'self->extra' got mutated and 'element' may not have | |
| // sufficient space to hold the next iteration's item. | |
| expected_count = self->extra->length; | |
| if (element_resize(element, expected_count) < 0) { | |
| Py_DECREF(child); | |
| element->extra->length = i; | |
| goto error; | |
| } | |
| } | |
| element->extra->children[i] = child; | |
| } | |
| assert(!element->extra->length); | |
| // The original 'self->extra' may be gone at this point if deepcopy() | |
| // has side-effects. However, 'i' is the number of copied items that | |
| // we were able to successfully copy. | |
| element->extra->length = i; | |
| } | |
| /* add object to memo dictionary (so deepcopy won't visit it again) */ | |
| id = PyLong_FromSsize_t((uintptr_t) self); | |
| if (!id) | |
| goto error; | |
| i = PyDict_SetItem(memo, id, (PyObject*) element); | |
| Py_DECREF(id); | |
| if (i < 0) | |
| goto error; | |
| _Py_LeaveRecursiveCall(); | |
| return (PyObject*) element; | |
| error: | |
| _Py_LeaveRecursiveCall(); | |
| Py_XDECREF(element); | |
| return NULL; | |
| } | |
| LOCAL(PyObject *) | |
| deepcopy(elementtreestate *st, PyObject *object, PyObject *memo) | |
| { | |
| /* do a deep copy of the given object */ | |
| /* Fast paths */ | |
| if (object == Py_None || PyUnicode_CheckExact(object)) { | |
| return Py_NewRef(object); | |
| } | |
| if (_PyObject_IsUniquelyReferenced(object)) { | |
| if (PyDict_CheckExact(object)) { | |
| PyObject *key, *value; | |
| Py_ssize_t pos = 0; | |
| int simple = 1; | |
| while (PyDict_Next(object, &pos, &key, &value)) { | |
| if (!PyUnicode_CheckExact(key) || !PyUnicode_CheckExact(value)) { | |
| simple = 0; | |
| break; | |
| } | |
| } | |
| if (simple) { | |
| return PyDict_Copy(object); | |
| } | |
| /* Fall through to general case */ | |
| } | |
| else if (Element_CheckExact(st, object)) { | |
| // The __deepcopy__() call may call arbitrary code even if the | |
| // object to copy is a built-in XML element (one of its children | |
| // any of its parents in its own __deepcopy__() implementation). | |
| Py_INCREF(object); | |
| PyObject *res = _elementtree_Element___deepcopy___impl( | |
| (ElementObject *)object, memo); | |
| Py_DECREF(object); | |
| return res; | |
| } | |
| } | |
| /* General case */ | |
| if (!st->deepcopy_obj) { | |
| PyErr_SetString(PyExc_RuntimeError, | |
| "deepcopy helper not found"); | |
| return NULL; | |
| } | |
| Py_INCREF(object); | |
| PyObject *args[2] = {object, memo}; | |
| PyObject *res = PyObject_Vectorcall(st->deepcopy_obj, args, 2, NULL); | |
| Py_DECREF(object); | |
| return res; | |
| } | |
| /*[clinic input] | |
| _elementtree.Element.__sizeof__ -> size_t | |
| [clinic start generated code]*/ | |
| static size_t | |
| _elementtree_Element___sizeof___impl(ElementObject *self) | |
| /*[clinic end generated code: output=baae4e7ae9fe04ec input=54e298c501f3e0d0]*/ | |
| { | |
| size_t result = _PyObject_SIZE(Py_TYPE(self)); | |
| if (self->extra) { | |
| result += sizeof(ElementObjectExtra); | |
| if (self->extra->children != self->extra->_children) { | |
| result += (size_t)self->extra->allocated * sizeof(PyObject*); | |
| } | |
| } | |
| return result; | |
| } | |
| /* dict keys for getstate/setstate. */ | |
| #define PICKLED_TAG "tag" | |
| #define PICKLED_CHILDREN "_children" | |
| #define PICKLED_ATTRIB "attrib" | |
| #define PICKLED_TAIL "tail" | |
| #define PICKLED_TEXT "text" | |
| /* __getstate__ returns a fabricated instance dict as in the pure-Python | |
| * Element implementation, for interoperability/interchangeability. This | |
| * makes the pure-Python implementation details an API, but (a) there aren't | |
| * any unnecessary structures there; and (b) it buys compatibility with 3.2 | |
| * pickles. See issue #16076. | |
| */ | |
| /*[clinic input] | |
| _elementtree.Element.__getstate__ | |
| [clinic start generated code]*/ | |
| static PyObject * | |
| _elementtree_Element___getstate___impl(ElementObject *self) | |
| /*[clinic end generated code: output=37279aeeb6bb5b04 input=f0d16d7ec2f7adc1]*/ | |
| { | |
| Py_ssize_t i; | |
| PyObject *children, *attrib; | |
| /* Build a list of children. */ | |
| children = PyList_New(self->extra ? self->extra->length : 0); | |
| if (!children) | |
| return NULL; | |
| for (i = 0; i < PyList_GET_SIZE(children); i++) { | |
| PyObject *child = Py_NewRef(self->extra->children[i]); | |
| PyList_SET_ITEM(children, i, child); | |
| } | |
| if (self->extra && self->extra->attrib) { | |
| attrib = Py_NewRef(self->extra->attrib); | |
| } | |
| else { | |
| attrib = PyDict_New(); | |
| if (!attrib) { | |
| Py_DECREF(children); | |
| return NULL; | |
| } | |
| } | |
| return Py_BuildValue("{sOsNsNsOsO}", | |
| PICKLED_TAG, self->tag, | |
| PICKLED_CHILDREN, children, | |
| PICKLED_ATTRIB, attrib, | |
| PICKLED_TEXT, JOIN_OBJ(self->text), | |
| PICKLED_TAIL, JOIN_OBJ(self->tail)); | |
| } | |
| static PyObject * | |
| element_setstate_from_attributes(elementtreestate *st, | |
| ElementObject *self, | |
| PyObject *tag, | |
| PyObject *attrib, | |
| PyObject *text, | |
| PyObject *tail, | |
| PyObject *children) | |
| { | |
| Py_ssize_t i, nchildren; | |
| ElementObjectExtra *oldextra = NULL; | |
| if (!tag) { | |
| PyErr_SetString(PyExc_TypeError, "tag may not be NULL"); | |
| return NULL; | |
| } | |
| Py_XSETREF(self->tag, Py_NewRef(tag)); | |
| text = text ? JOIN_SET(text, PyList_CheckExact(text)) : Py_None; | |
| Py_INCREF(JOIN_OBJ(text)); | |
| _set_joined_ptr(&self->text, text); | |
| tail = tail ? JOIN_SET(tail, PyList_CheckExact(tail)) : Py_None; | |
| Py_INCREF(JOIN_OBJ(tail)); | |
| _set_joined_ptr(&self->tail, tail); | |
| /* Handle ATTRIB and CHILDREN. */ | |
| if (!children && !attrib) { | |
| Py_RETURN_NONE; | |
| } | |
| /* Compute 'nchildren'. */ | |
| if (children) { | |
| if (!PyList_Check(children)) { | |
| PyErr_SetString(PyExc_TypeError, "'_children' is not a list"); | |
| return NULL; | |
| } | |
| nchildren = PyList_GET_SIZE(children); | |
| /* (Re-)allocate 'extra'. | |
| Avoid DECREFs calling into this code again (cycles, etc.) | |
| */ | |
| oldextra = self->extra; | |
| self->extra = NULL; | |
| if (element_resize(self, nchildren)) { | |
| assert(!self->extra || !self->extra->length); | |
| clear_extra(self); | |
| self->extra = oldextra; | |
| return NULL; | |
| } | |
| assert(self->extra); | |
| assert(self->extra->allocated >= nchildren); | |
| if (oldextra) { | |
| assert(self->extra->attrib == NULL); | |
| self->extra->attrib = oldextra->attrib; | |
| oldextra->attrib = NULL; | |
| } | |
| /* Copy children */ | |
| for (i = 0; i < nchildren; i++) { | |
| PyObject *child = PyList_GET_ITEM(children, i); | |
| if (!Element_Check(st, child)) { | |
| raise_type_error(child); | |
| self->extra->length = i; | |
| dealloc_extra(oldextra); | |
| return NULL; | |
| } | |
| self->extra->children[i] = Py_NewRef(child); | |
| } | |
| assert(!self->extra->length); | |
| self->extra->length = nchildren; | |
| } | |
| else { | |
| if (element_resize(self, 0)) { | |
| return NULL; | |
| } | |
| } | |
| /* Stash attrib. */ | |
| Py_XSETREF(self->extra->attrib, Py_XNewRef(attrib)); | |
| dealloc_extra(oldextra); | |
| Py_RETURN_NONE; | |
| } | |
| /* __setstate__ for Element instance from the Python implementation. | |
| * 'state' should be the instance dict. | |
| */ | |
| static PyObject * | |
| element_setstate_from_Python(elementtreestate *st, ElementObject *self, | |
| PyObject *state) | |
| { | |
| static char *kwlist[] = {PICKLED_TAG, PICKLED_ATTRIB, PICKLED_TEXT, | |
| PICKLED_TAIL, PICKLED_CHILDREN, 0}; | |
| PyObject *args; | |
| PyObject *tag, *attrib, *text, *tail, *children; | |
| PyObject *retval; | |
| tag = attrib = text = tail = children = NULL; | |
| args = PyTuple_New(0); | |
| if (!args) | |
| return NULL; | |
| if (PyArg_ParseTupleAndKeywords(args, state, "|$OOOOO", kwlist, &tag, | |
| &attrib, &text, &tail, &children)) | |
| retval = element_setstate_from_attributes(st, self, tag, attrib, text, | |
| tail, children); | |
| else | |
| retval = NULL; | |
| Py_DECREF(args); | |
| return retval; | |
| } | |
| /*[clinic input] | |
| _elementtree.Element.__setstate__ | |
| cls: defining_class | |
| state: object | |
| / | |
| [clinic start generated code]*/ | |
| static PyObject * | |
| _elementtree_Element___setstate___impl(ElementObject *self, | |
| PyTypeObject *cls, PyObject *state) | |
| /*[clinic end generated code: output=598bfb5730f71509 input=13830488d35d51f7]*/ | |
| { | |
| if (!PyDict_CheckExact(state)) { | |
| PyErr_Format(PyExc_TypeError, | |
| "Don't know how to unpickle \"%.200R\" as an Element", | |
| state); | |
| return NULL; | |
| } | |
| else { | |
| elementtreestate *st = get_elementtree_state_by_cls(cls); | |
| return element_setstate_from_Python(st, self, state); | |
| } | |
| } | |
| LOCAL(int) | |
| checkpath(PyObject* tag) | |
| { | |
| Py_ssize_t i; | |
| int check = 1; | |
| /* check if a tag contains an xpath character */ | |
| #define PATHCHAR(ch) \ | |
| (ch == '/' || ch == '*' || ch == '[' || ch == '@' || ch == '.') | |
| if (PyUnicode_Check(tag)) { | |
| const Py_ssize_t len = PyUnicode_GET_LENGTH(tag); | |
| const void *data = PyUnicode_DATA(tag); | |
| int kind = PyUnicode_KIND(tag); | |
| if (len >= 3 && PyUnicode_READ(kind, data, 0) == '{' && ( | |
| PyUnicode_READ(kind, data, 1) == '}' || ( | |
| PyUnicode_READ(kind, data, 1) == '*' && | |
| PyUnicode_READ(kind, data, 2) == '}'))) { | |
| /* wildcard: '{}tag' or '{*}tag' */ | |
| return 1; | |
| } | |
| for (i = 0; i < len; i++) { | |
| Py_UCS4 ch = PyUnicode_READ(kind, data, i); | |
| if (ch == '{') | |
| check = 0; | |
| else if (ch == '}') | |
| check = 1; | |
| else if (check && PATHCHAR(ch)) | |
| return 1; | |
| } | |
| return 0; | |
| } | |
| return 1; /* unknown type; might be path expression */ | |
| } | |
| /*[clinic input] | |
| _elementtree.Element.extend | |
| cls: defining_class | |
| elements: object | |
| / | |
| Append subelements from a sequence. | |
| *elements* is a sequence with zero or more elements. | |
| [clinic start generated code]*/ | |
| static PyObject * | |
| _elementtree_Element_extend_impl(ElementObject *self, PyTypeObject *cls, | |
| PyObject *elements) | |
| /*[clinic end generated code: output=3e86d37fac542216 input=401ac1d07e13282b]*/ | |
| { | |
| PyObject* seq; | |
| Py_ssize_t i; | |
| seq = PySequence_Fast(elements, "'elements' must be an iterable"); | |
| if (!seq) { | |
| return NULL; | |
| } | |
| elementtreestate *st = get_elementtree_state_by_cls(cls); | |
| for (i = 0; i < PySequence_Fast_GET_SIZE(seq); i++) { | |
| PyObject* element = Py_NewRef(PySequence_Fast_GET_ITEM(seq, i)); | |
| if (element_add_subelement(st, self, element) < 0) { | |
| Py_DECREF(seq); | |
| Py_DECREF(element); | |
| return NULL; | |
| } | |
| Py_DECREF(element); | |
| } | |
| Py_DECREF(seq); | |
| Py_RETURN_NONE; | |
| } | |
| /*[clinic input] | |
| _elementtree.Element.find | |
| cls: defining_class | |
| / | |
| path: object | |
| namespaces: object = None | |
| Find first matching element by tag name or path. | |
| *path* is a string having either an element tag or an XPath, | |
| *namespaces* is an optional mapping from namespace prefix to full | |
| name. | |
| Return the first matching element, or None if no element was found. | |
| [clinic start generated code]*/ | |
| static PyObject * | |
| _elementtree_Element_find_impl(ElementObject *self, PyTypeObject *cls, | |
| PyObject *path, PyObject *namespaces) | |
| /*[clinic end generated code: output=18f77d393c9fef1b input=3aec422879a342e1]*/ | |
| { | |
| elementtreestate *st = get_elementtree_state_by_cls(cls); | |
| if (checkpath(path) || namespaces != Py_None) { | |
| return PyObject_CallMethodObjArgs( | |
| st->elementpath_obj, st->str_find, self, path, namespaces, NULL | |
| ); | |
| } | |
| for (Py_ssize_t i = 0; self->extra && i < self->extra->length; i++) { | |
| PyObject *item = self->extra->children[i]; | |
| assert(Element_Check(st, item)); | |
| Py_INCREF(item); | |
| PyObject *tag = Py_NewRef(((ElementObject *)item)->tag); | |
| int rc = PyObject_RichCompareBool(tag, path, Py_EQ); | |
| Py_DECREF(tag); | |
| if (rc > 0) { | |
| return item; | |
| } | |
| Py_DECREF(item); | |
| if (rc < 0) { | |
| return NULL; | |
| } | |
| } | |
| Py_RETURN_NONE; | |
| } | |
| /*[clinic input] | |
| _elementtree.Element.findtext | |
| cls: defining_class | |
| / | |
| path: object | |
| default: object = None | |
| namespaces: object = None | |
| Find text for first matching element by tag name or path. | |
| *path* is a string having either an element tag or an XPath, | |
| *default* is the value to return if the element was not found, | |
| *namespaces* is an optional mapping from namespace prefix to full | |
| name. | |
| Return text content of first matching element, or default value if | |
| none was found. Note that if an element is found having no text | |
| content, the empty string is returned. | |
| [clinic start generated code]*/ | |
| static PyObject * | |
| _elementtree_Element_findtext_impl(ElementObject *self, PyTypeObject *cls, | |
| PyObject *path, PyObject *default_value, | |
| PyObject *namespaces) | |
| /*[clinic end generated code: output=6af7a2d96aac32cb input=64610701f762c4f5]*/ | |
| { | |
| elementtreestate *st = get_elementtree_state_by_cls(cls); | |
| if (checkpath(path) || namespaces != Py_None) | |
| return PyObject_CallMethodObjArgs( | |
| st->elementpath_obj, st->str_findtext, | |
| self, path, default_value, namespaces, NULL | |
| ); | |
| for (Py_ssize_t i = 0; self->extra && i < self->extra->length; i++) { | |
| PyObject *item = self->extra->children[i]; | |
| assert(Element_Check(st, item)); | |
| Py_INCREF(item); | |
| PyObject *tag = Py_NewRef(((ElementObject *)item)->tag); | |
| int rc = PyObject_RichCompareBool(tag, path, Py_EQ); | |
| Py_DECREF(tag); | |
| if (rc > 0) { | |
| PyObject *text = element_get_text((ElementObject *)item); | |
| Py_DECREF(item); | |
| if (text == Py_None) { | |
| Py_DECREF(text); | |
| return Py_GetConstant(Py_CONSTANT_EMPTY_STR); | |
| } | |
| return text; | |
| } | |
| Py_DECREF(item); | |
| if (rc < 0) { | |
| return NULL; | |
| } | |
| } | |
| return Py_NewRef(default_value); | |
| } | |
| /*[clinic input] | |
| _elementtree.Element.findall | |
| cls: defining_class | |
| / | |
| path: object | |
| namespaces: object = None | |
| Find all matching subelements by tag name or path. | |
| *path* is a string having either an element tag or an XPath, | |
| *namespaces* is an optional mapping from namespace prefix to full | |
| name. | |
| Returns list containing all matching elements in document order. | |
| [clinic start generated code]*/ | |
| static PyObject * | |
| _elementtree_Element_findall_impl(ElementObject *self, PyTypeObject *cls, | |
| PyObject *path, PyObject *namespaces) | |
| /*[clinic end generated code: output=65e39a1208f3b59e input=2208ddeb5f1cc7c3]*/ | |
| { | |
| elementtreestate *st = get_elementtree_state_by_cls(cls); | |
| if (checkpath(path) || namespaces != Py_None) { | |
| return PyObject_CallMethodObjArgs( | |
| st->elementpath_obj, st->str_findall, self, path, namespaces, NULL | |
| ); | |
| } | |
| PyObject *out = PyList_New(0); | |
| if (out == NULL) { | |
| return NULL; | |
| } | |
| for (Py_ssize_t i = 0; self->extra && i < self->extra->length; i++) { | |
| PyObject *item = self->extra->children[i]; | |
| assert(Element_Check(st, item)); | |
| Py_INCREF(item); | |
| PyObject *tag = Py_NewRef(((ElementObject *)item)->tag); | |
| int rc = PyObject_RichCompareBool(tag, path, Py_EQ); | |
| Py_DECREF(tag); | |
| if (rc != 0 && (rc < 0 || PyList_Append(out, item) < 0)) { | |
| Py_DECREF(item); | |
| Py_DECREF(out); | |
| return NULL; | |
| } | |
| Py_DECREF(item); | |
| } | |
| return out; | |
| } | |
| /*[clinic input] | |
| _elementtree.Element.iterfind | |
| cls: defining_class | |
| / | |
| path: object | |
| namespaces: object = None | |
| Find all matching subelements by tag name or path. | |
| *path* is a string having either an element tag or an XPath, | |
| *namespaces* is an optional mapping from namespace prefix to full | |
| name. | |
| Return an iterable yielding all matching elements in document order. | |
| [clinic start generated code]*/ | |
| static PyObject * | |
| _elementtree_Element_iterfind_impl(ElementObject *self, PyTypeObject *cls, | |
| PyObject *path, PyObject *namespaces) | |
| /*[clinic end generated code: output=be5c3f697a14e676 input=00bea06334260582]*/ | |
| { | |
| PyObject* tag = path; | |
| elementtreestate *st = get_elementtree_state_by_cls(cls); | |
| return PyObject_CallMethodObjArgs( | |
| st->elementpath_obj, st->str_iterfind, self, tag, namespaces, NULL); | |
| } | |
| /*[clinic input] | |
| _elementtree.Element.get | |
| key: object | |
| default: object = None | |
| Get element attribute. | |
| Equivalent to attrib.get, but some implementations may handle this a | |
| bit more efficiently. *key* is what attribute to look for, and | |
| *default* is what to return if the attribute was not found. | |
| Returns a string containing the attribute value, or the default if | |
| attribute was not found. | |
| [clinic start generated code]*/ | |
| static PyObject * | |
| _elementtree_Element_get_impl(ElementObject *self, PyObject *key, | |
| PyObject *default_value) | |
| /*[clinic end generated code: output=523c614142595d75 input=332624526ef81a70]*/ | |
| { | |
| if (self->extra && self->extra->attrib) { | |
| PyObject *attrib = Py_NewRef(self->extra->attrib); | |
| PyObject *value; | |
| if (PyDict_GetItemRef(attrib, key, &value) != 0) { | |
| Py_DECREF(attrib); | |
| // The key exists or an error occurred | |
| return value; | |
| } | |
| Py_DECREF(attrib); | |
| } | |
| return Py_NewRef(default_value); | |
| } | |
| static PyObject * | |
| create_elementiter(elementtreestate *st, ElementObject *self, PyObject *tag, | |
| int gettext); | |
| /*[clinic input] | |
| _elementtree.Element.iter | |
| cls: defining_class | |
| / | |
| tag: object = None | |
| Create tree iterator. | |
| The iterator loops over the element and all subelements in document | |
| order, returning all elements with a matching tag. | |
| If the tree structure is modified during iteration, new or removed | |
| elements may or may not be included. To get a stable set, use the | |
| list() function on the iterator, and loop over the resulting list. | |
| *tag* is what tags to look for (default is to return all elements) | |
| Return an iterator containing all the matching elements. | |
| [clinic start generated code]*/ | |
| static PyObject * | |
| _elementtree_Element_iter_impl(ElementObject *self, PyTypeObject *cls, | |
| PyObject *tag) | |
| /*[clinic end generated code: output=bff29dc5d4566c68 input=e4c542a12e6f9199]*/ | |
| { | |
| if (PyUnicode_Check(tag)) { | |
| if (PyUnicode_GET_LENGTH(tag) == 1 && PyUnicode_READ_CHAR(tag, 0) == '*') | |
| tag = Py_None; | |
| } | |
| elementtreestate *st = get_elementtree_state_by_cls(cls); | |
| return create_elementiter(st, self, tag, 0); | |
| } | |
| /*[clinic input] | |
| _elementtree.Element.itertext | |
| cls: defining_class | |
| / | |
| Create text iterator. | |
| The iterator loops over the element and all subelements in document | |
| order, returning all inner text. | |
| [clinic start generated code]*/ | |
| static PyObject * | |
| _elementtree_Element_itertext_impl(ElementObject *self, PyTypeObject *cls) | |
| /*[clinic end generated code: output=fdeb2a3bca0ae063 input=eaffe70224da7f02]*/ | |
| { | |
| elementtreestate *st = get_elementtree_state_by_cls(cls); | |
| return create_elementiter(st, self, Py_None, 1); | |
| } | |
| static PyObject* | |
| element_getitem(PyObject *op, Py_ssize_t index) | |
| { | |
| ElementObject *self = _Element_CAST(op); | |
| if (!self->extra || index < 0 || index >= self->extra->length) { | |
| PyErr_SetString( | |
| PyExc_IndexError, | |
| "child index out of range" | |
| ); | |
| return NULL; | |
| } | |
| return Py_NewRef(self->extra->children[index]); | |
| } | |
| static int | |
| element_bool(PyObject *op) | |
| { | |
| ElementObject *self = _Element_CAST(op); | |
| if (PyErr_WarnEx(PyExc_DeprecationWarning, | |
| "Testing an element's truth value will always return True " | |
| "in future versions. Use specific 'len(elem)' or " | |
| "'elem is not None' test instead.", | |
| 1) < 0) { | |
| return -1; | |
| }; | |
| if (self->extra ? self->extra->length : 0) { | |
| return 1; | |
| } | |
| return 0; | |
| } | |
| /*[clinic input] | |
| _elementtree.Element.insert | |
| index: Py_ssize_t | |
| subelement: object(subclass_of='clinic_state()->Element_Type') | |
| / | |
| Insert *subelement* at position *index*. | |
| [clinic start generated code]*/ | |
| static PyObject * | |
| _elementtree_Element_insert_impl(ElementObject *self, Py_ssize_t index, | |
| PyObject *subelement) | |
| /*[clinic end generated code: output=990adfef4d424c0b input=2886a2266de15ed7]*/ | |
| { | |
| Py_ssize_t i; | |
| if (!self->extra) { | |
| if (create_extra(self, NULL) < 0) | |
| return NULL; | |
| } | |
| if (index < 0) { | |
| index += self->extra->length; | |
| if (index < 0) | |
| index = 0; | |
| } | |
| if (index > self->extra->length) | |
| index = self->extra->length; | |
| if (element_resize(self, 1) < 0) | |
| return NULL; | |
| for (i = self->extra->length; i > index; i--) | |
| self->extra->children[i] = self->extra->children[i-1]; | |
| self->extra->children[index] = Py_NewRef(subelement); | |
| self->extra->length++; | |
| Py_RETURN_NONE; | |
| } | |
| /*[clinic input] | |
| _elementtree.Element.items | |
| Get element attributes as (name, value) pairs. | |
| Equivalent to attrib.items(). | |
| [clinic start generated code]*/ | |
| static PyObject * | |
| _elementtree_Element_items_impl(ElementObject *self) | |
| /*[clinic end generated code: output=6db2c778ce3f5a4d input=7b5adcd8f774d4e0]*/ | |
| { | |
| if (!self->extra || !self->extra->attrib) | |
| return PyList_New(0); | |
| return PyDict_Items(self->extra->attrib); | |
| } | |
| /*[clinic input] | |
| _elementtree.Element.keys | |
| Get attribute names. | |
| Equivalent to attrib.keys() | |
| [clinic start generated code]*/ | |
| static PyObject * | |
| _elementtree_Element_keys_impl(ElementObject *self) | |
| /*[clinic end generated code: output=bc5bfabbf20eeb3c input=6d860fbdb565115d]*/ | |
| { | |
| if (!self->extra || !self->extra->attrib) | |
| return PyList_New(0); | |
| return PyDict_Keys(self->extra->attrib); | |
| } | |
| static Py_ssize_t | |
| element_length(PyObject *op) | |
| { | |
| ElementObject *self = _Element_CAST(op); | |
| if (!self->extra) | |
| return 0; | |
| return self->extra->length; | |
| } | |
| /*[clinic input] | |
| _elementtree.Element.makeelement | |
| cls: defining_class | |
| tag: object | |
| attrib: object(subclass_of='&PyDict_Type') | |
| / | |
| Create a new element with the same type. | |
| *tag* is a string containing the element name. *attrib* is a | |
| dictionary containing the element attributes. | |
| Do not call this method, use the SubElement factory function | |
| instead. | |
| [clinic start generated code]*/ | |
| static PyObject * | |
| _elementtree_Element_makeelement_impl(ElementObject *self, PyTypeObject *cls, | |
| PyObject *tag, PyObject *attrib) | |
| /*[clinic end generated code: output=d50bb17a47077d47 input=02b62a503fa25ae7]*/ | |
| { | |
| PyObject* elem; | |
| attrib = PyDict_Copy(attrib); | |
| if (!attrib) | |
| return NULL; | |
| elementtreestate *st = get_elementtree_state_by_cls(cls); | |
| elem = create_new_element(st, tag, attrib); | |
| Py_DECREF(attrib); | |
| return elem; | |
| } | |
| /*[clinic input] | |
| _elementtree.Element.remove | |
| subelement: object(subclass_of='clinic_state()->Element_Type') | |
| / | |
| Remove matching subelement. | |
| Unlike the find methods, this method compares elements based on | |
| identity, NOT ON tag value or contents. To remove subelements by | |
| other means, the easiest way is to use a list comprehension to | |
| select what elements to keep, and then use slice assignment to | |
| update the parent element. | |
| ValueError is raised if a matching element could not be found. | |
| [clinic start generated code]*/ | |
| static PyObject * | |
| _elementtree_Element_remove_impl(ElementObject *self, PyObject *subelement) | |
| /*[clinic end generated code: output=38fe6c07d6d87d1f input=e035af9d920f785b]*/ | |
| { | |
| Py_ssize_t i; | |
| // When iterating over the list of children, we need to check that the | |
| // list is not cleared (self->extra != NULL) and that we are still within | |
| // the correct bounds (i < self->extra->length). | |
| // | |
| // We deliberately avoid protecting against children lists that grow | |
| // faster than the index since list objects do not protect against it. | |
| int rc = 0; | |
| for (i = 0; self->extra && i < self->extra->length; i++) { | |
| if (self->extra->children[i] == subelement) { | |
| rc = 1; | |
| break; | |
| } | |
| PyObject *child = Py_NewRef(self->extra->children[i]); | |
| rc = PyObject_RichCompareBool(child, subelement, Py_EQ); | |
| Py_DECREF(child); | |
| if (rc < 0) { | |
| return NULL; | |
| } | |
| else if (rc > 0) { | |
| break; | |
| } | |
| } | |
| if (rc == 0) { | |
| PyErr_Format(PyExc_ValueError, "%R not in %R", subelement, self); | |
| return NULL; | |
| } | |
| // An extra check must be done if the mutation occurs at the very last | |
| // step and removes or clears the 'extra' list (the condition on the | |
| // length would not be satisfied any more). | |
| if (self->extra == NULL || i >= self->extra->length) { | |
| Py_RETURN_NONE; | |
| } | |
| PyObject *found = self->extra->children[i]; | |
| self->extra->length--; | |
| for (; i < self->extra->length; i++) { | |
| self->extra->children[i] = self->extra->children[i+1]; | |
| } | |
| Py_DECREF(found); | |
| Py_RETURN_NONE; | |
| } | |
| static PyObject* | |
| element_repr(PyObject *op) | |
| { | |
| int status; | |
| ElementObject *self = _Element_CAST(op); | |
| if (self->tag == NULL) | |
| return PyUnicode_FromFormat("<Element at %p>", self); | |
| status = Py_ReprEnter((PyObject *)self); | |
| if (status == 0) { | |
| PyObject *res; | |
| res = PyUnicode_FromFormat("<Element %R at %p>", self->tag, self); | |
| Py_ReprLeave((PyObject *)self); | |
| return res; | |
| } | |
| if (status > 0) | |
| PyErr_Format(PyExc_RuntimeError, | |
| "reentrant call inside %s.__repr__", | |
| Py_TYPE(self)->tp_name); | |
| return NULL; | |
| } | |
| /*[clinic input] | |
| _elementtree.Element.set | |
| key: object | |
| value: object | |
| / | |
| Set element attribute. | |
| Equivalent to attrib[key] = value, but some implementations may | |
| handle this a bit more efficiently. *key* is what attribute to set, | |
| and *value* is the attribute value to set it to. | |
| [clinic start generated code]*/ | |
| static PyObject * | |
| _elementtree_Element_set_impl(ElementObject *self, PyObject *key, | |
| PyObject *value) | |
| /*[clinic end generated code: output=fb938806be3c5656 input=bbaadd68b86636d7]*/ | |
| { | |
| PyObject* attrib; | |
| if (!self->extra) { | |
| if (create_extra(self, NULL) < 0) | |
| return NULL; | |
| } | |
| attrib = element_get_attrib(self); | |
| if (!attrib) | |
| return NULL; | |
| if (PyDict_SetItem(attrib, key, value) < 0) | |
| return NULL; | |
| Py_RETURN_NONE; | |
| } | |
| static int | |
| element_setitem(PyObject *op, Py_ssize_t index, PyObject* item) | |
| { | |
| ElementObject *self = _Element_CAST(op); | |
| Py_ssize_t i; | |
| PyObject* old; | |
| if (!self->extra || index < 0 || index >= self->extra->length) { | |
| PyErr_SetString( | |
| PyExc_IndexError, | |
| "child assignment index out of range"); | |
| return -1; | |
| } | |
| old = self->extra->children[index]; | |
| if (item) { | |
| PyTypeObject *tp = Py_TYPE(self); | |
| elementtreestate *st = get_elementtree_state_by_type(tp); | |
| if (!Element_Check(st, item)) { | |
| raise_type_error(item); | |
| return -1; | |
| } | |
| self->extra->children[index] = Py_NewRef(item); | |
| } else { | |
| self->extra->length--; | |
| for (i = index; i < self->extra->length; i++) | |
| self->extra->children[i] = self->extra->children[i+1]; | |
| } | |
| Py_DECREF(old); | |
| return 0; | |
| } | |
| static PyObject * | |
| element_subscr(PyObject *op, PyObject *item) | |
| { | |
| ElementObject *self = _Element_CAST(op); | |
| if (PyIndex_Check(item)) { | |
| Py_ssize_t i = PyNumber_AsSsize_t(item, PyExc_IndexError); | |
| if (i == -1 && PyErr_Occurred()) { | |
| return NULL; | |
| } | |
| if (i < 0 && self->extra) | |
| i += self->extra->length; | |
| return element_getitem(op, i); | |
| } | |
| else if (PySlice_Check(item)) { | |
| // Note: 'slicelen' is computed once we are sure that 'self->extra' | |
| // cannot be mutated by user-defined code. | |
| // See https://github.com/python/cpython/issues/143200. | |
| Py_ssize_t start, stop, step, slicelen, i; | |
| size_t cur; | |
| PyObject* list; | |
| if (PySlice_Unpack(item, &start, &stop, &step) < 0) { | |
| return NULL; | |
| } | |
| if (self->extra == NULL) { | |
| return PyList_New(0); | |
| } | |
| slicelen = PySlice_AdjustIndices(self->extra->length, &start, &stop, | |
| step); | |
| if (slicelen <= 0) | |
| return PyList_New(0); | |
| else { | |
| list = PyList_New(slicelen); | |
| if (!list) | |
| return NULL; | |
| for (cur = start, i = 0; i < slicelen; | |
| cur += step, i++) { | |
| PyObject* item = Py_NewRef(self->extra->children[cur]); | |
| PyList_SET_ITEM(list, i, item); | |
| } | |
| return list; | |
| } | |
| } | |
| else { | |
| PyErr_SetString(PyExc_TypeError, | |
| "element indices must be integers"); | |
| return NULL; | |
| } | |
| } | |
| static int | |
| element_ass_subscr(PyObject *op, PyObject *item, PyObject *value) | |
| { | |
| ElementObject *self = _Element_CAST(op); | |
| if (PyIndex_Check(item)) { | |
| Py_ssize_t i = PyNumber_AsSsize_t(item, PyExc_IndexError); | |
| if (i == -1 && PyErr_Occurred()) { | |
| return -1; | |
| } | |
| if (i < 0 && self->extra) | |
| i += self->extra->length; | |
| return element_setitem(op, i, value); | |
| } | |
| else if (PySlice_Check(item)) { | |
| // Note: 'slicelen' is computed once we are sure that 'self->extra' | |
| // cannot be mutated by user-defined code. | |
| // See https://github.com/python/cpython/issues/143200. | |
| Py_ssize_t start, stop, step, slicelen, newlen, i; | |
| size_t cur; | |
| PyObject* recycle = NULL; | |
| PyObject* seq; | |
| if (PySlice_Unpack(item, &start, &stop, &step) < 0) { | |
| return -1; | |
| } | |
| if (value == NULL) { | |
| /* Delete slice */ | |
| if (self->extra == NULL) { | |
| return 0; | |
| } | |
| slicelen = PySlice_AdjustIndices(self->extra->length, &start, &stop, | |
| step); | |
| if (slicelen <= 0) | |
| return 0; | |
| /* Since we're deleting, the direction of the range doesn't matter, | |
| * so for simplicity make it always ascending. | |
| */ | |
| if (step < 0) { | |
| stop = start + 1; | |
| start = stop + step * (slicelen - 1) - 1; | |
| step = -step; | |
| } | |
| assert((size_t)slicelen <= SIZE_MAX / sizeof(PyObject *)); | |
| /* recycle is a list that will contain all the children | |
| * scheduled for removal. | |
| */ | |
| if (!(recycle = PyList_New(slicelen))) { | |
| return -1; | |
| } | |
| /* This loop walks over all the children that have to be deleted, | |
| * with cur pointing at them. num_moved is the amount of children | |
| * until the next deleted child that have to be "shifted down" to | |
| * occupy the deleted's places. | |
| * Note that in the ith iteration, shifting is done i+i places down | |
| * because i children were already removed. | |
| */ | |
| for (cur = start, i = 0; cur < (size_t)stop; cur += step, ++i) { | |
| /* Compute how many children have to be moved, clipping at the | |
| * list end. | |
| */ | |
| Py_ssize_t num_moved = step - 1; | |
| if (cur + step >= (size_t)self->extra->length) { | |
| num_moved = self->extra->length - cur - 1; | |
| } | |
| PyList_SET_ITEM(recycle, i, self->extra->children[cur]); | |
| memmove( | |
| self->extra->children + cur - i, | |
| self->extra->children + cur + 1, | |
| num_moved * sizeof(PyObject *)); | |
| } | |
| /* Leftover "tail" after the last removed child */ | |
| cur = start + (size_t)slicelen * step; | |
| if (cur < (size_t)self->extra->length) { | |
| memmove( | |
| self->extra->children + cur - slicelen, | |
| self->extra->children + cur, | |
| (self->extra->length - cur) * sizeof(PyObject *)); | |
| } | |
| self->extra->length -= slicelen; | |
| /* Discard the recycle list with all the deleted sub-elements */ | |
| Py_DECREF(recycle); | |
| return 0; | |
| } | |
| /* A new slice is actually being assigned */ | |
| seq = PySequence_Fast(value, "assignment expects an iterable"); | |
| if (!seq) { | |
| return -1; | |
| } | |
| newlen = PySequence_Fast_GET_SIZE(seq); | |
| if (self->extra == NULL) { | |
| if (create_extra(self, NULL) < 0) { | |
| Py_DECREF(seq); | |
| return -1; | |
| } | |
| } | |
| slicelen = PySlice_AdjustIndices(self->extra->length, &start, &stop, | |
| step); | |
| if (step != 1 && newlen != slicelen) { | |
| Py_DECREF(seq); | |
| PyErr_Format(PyExc_ValueError, | |
| "attempt to assign sequence of size %zd " | |
| "to extended slice of size %zd", | |
| newlen, slicelen | |
| ); | |
| return -1; | |
| } | |
| /* Resize before creating the recycle bin, to prevent refleaks. */ | |
| if (newlen > slicelen) { | |
| if (element_resize(self, newlen - slicelen) < 0) { | |
| Py_DECREF(seq); | |
| return -1; | |
| } | |
| } | |
| PyTypeObject *tp = Py_TYPE(self); | |
| elementtreestate *st = get_elementtree_state_by_type(tp); | |
| for (i = 0; i < newlen; i++) { | |
| PyObject *element = PySequence_Fast_GET_ITEM(seq, i); | |
| if (!Element_Check(st, element)) { | |
| raise_type_error(element); | |
| Py_DECREF(seq); | |
| return -1; | |
| } | |
| } | |
| if (slicelen > 0) { | |
| /* to avoid recursive calls to this method (via decref), move | |
| old items to the recycle bin here, and get rid of them when | |
| we're done modifying the element */ | |
| recycle = PyList_New(slicelen); | |
| if (!recycle) { | |
| Py_DECREF(seq); | |
| return -1; | |
| } | |
| for (cur = start, i = 0; i < slicelen; | |
| cur += step, i++) | |
| PyList_SET_ITEM(recycle, i, self->extra->children[cur]); | |
| } | |
| if (newlen < slicelen) { | |
| /* delete slice */ | |
| for (i = stop; i < self->extra->length; i++) | |
| self->extra->children[i + newlen - slicelen] = self->extra->children[i]; | |
| } else if (newlen > slicelen) { | |
| /* insert slice */ | |
| for (i = self->extra->length-1; i >= stop; i--) | |
| self->extra->children[i + newlen - slicelen] = self->extra->children[i]; | |
| } | |
| /* replace the slice */ | |
| for (cur = start, i = 0; i < newlen; | |
| cur += step, i++) { | |
| PyObject* element = PySequence_Fast_GET_ITEM(seq, i); | |
| self->extra->children[cur] = Py_NewRef(element); | |
| } | |
| self->extra->length += newlen - slicelen; | |
| Py_DECREF(seq); | |
| /* discard the recycle bin, and everything in it */ | |
| Py_XDECREF(recycle); | |
| return 0; | |
| } | |
| else { | |
| PyErr_SetString(PyExc_TypeError, | |
| "element indices must be integers"); | |
| return -1; | |
| } | |
| } | |
| static PyObject* | |
| element_tag_getter(PyObject *op, void *closure) | |
| { | |
| ElementObject *self = _Element_CAST(op); | |
| PyObject *res = self->tag; | |
| return Py_NewRef(res); | |
| } | |
| static PyObject* | |
| element_text_getter(PyObject *op, void *closure) | |
| { | |
| ElementObject *self = _Element_CAST(op); | |
| return element_get_text(self); | |
| } | |
| static PyObject* | |
| element_tail_getter(PyObject *op, void *closure) | |
| { | |
| ElementObject *self = _Element_CAST(op); | |
| return element_get_tail(self); | |
| } | |
| static PyObject* | |
| element_attrib_getter(PyObject *op, void *closure) | |
| { | |
| PyObject *res; | |
| ElementObject *self = _Element_CAST(op); | |
| if (!self->extra) { | |
| if (create_extra(self, NULL) < 0) | |
| return NULL; | |
| } | |
| res = element_get_attrib(self); | |
| return Py_XNewRef(res); | |
| } | |
| /* macro for setter validation */ | |
| #define _VALIDATE_ATTR_VALUE(V) \ | |
| if ((V) == NULL) { \ | |
| PyErr_SetString( \ | |
| PyExc_AttributeError, \ | |
| "can't delete element attribute"); \ | |
| return -1; \ | |
| } | |
| static int | |
| element_tag_setter(PyObject *op, PyObject *value, void *closure) | |
| { | |
| _VALIDATE_ATTR_VALUE(value); | |
| ElementObject *self = _Element_CAST(op); | |
| Py_SETREF(self->tag, Py_NewRef(value)); | |
| return 0; | |
| } | |
| static int | |
| element_text_setter(PyObject *op, PyObject *value, void *closure) | |
| { | |
| _VALIDATE_ATTR_VALUE(value); | |
| ElementObject *self = _Element_CAST(op); | |
| _set_joined_ptr(&self->text, Py_NewRef(value)); | |
| return 0; | |
| } | |
| static int | |
| element_tail_setter(PyObject *op, PyObject *value, void *closure) | |
| { | |
| _VALIDATE_ATTR_VALUE(value); | |
| ElementObject *self = _Element_CAST(op); | |
| _set_joined_ptr(&self->tail, Py_NewRef(value)); | |
| return 0; | |
| } | |
| static int | |
| element_attrib_setter(PyObject *op, PyObject *value, void *closure) | |
| { | |
| _VALIDATE_ATTR_VALUE(value); | |
| if (!PyAnyDict_Check(value)) { | |
| PyErr_Format(PyExc_TypeError, | |
| "attrib must be dict or frozendict, not %T", | |
| value); | |
| return -1; | |
| } | |
| ElementObject *self = _Element_CAST(op); | |
| if (!self->extra) { | |
| if (create_extra(self, NULL) < 0) | |
| return -1; | |
| } | |
| Py_XSETREF(self->extra->attrib, Py_NewRef(value)); | |
| return 0; | |
| } | |
| /******************************* Element iterator ****************************/ | |
| /* ElementIterObject represents the iteration state over an XML element in | |
| * pre-order traversal. To keep track of which sub-element should be returned | |
| * next, a stack of parents is maintained. This is a standard stack-based | |
| * iterative pre-order traversal of a tree. | |
| * The stack is managed using a continuous array. | |
| * Each stack item contains the saved parent to which we should return after | |
| * the current one is exhausted, and the next child to examine in that parent. | |
| */ | |
| typedef struct ParentLocator_t { | |
| ElementObject *parent; | |
| Py_ssize_t child_index; | |
| } ParentLocator; | |
| typedef struct { | |
| PyObject_HEAD | |
| ParentLocator *parent_stack; | |
| Py_ssize_t parent_stack_used; | |
| Py_ssize_t parent_stack_size; | |
| ElementObject *root_element; | |
| PyObject *sought_tag; | |
| int gettext; | |
| } ElementIterObject; | |
| #define _ElementIter_CAST(op) ((ElementIterObject *)(op)) | |
| static void | |
| elementiter_dealloc(PyObject *op) | |
| { | |
| PyTypeObject *tp = Py_TYPE(op); | |
| ElementIterObject *it = _ElementIter_CAST(op); | |
| Py_ssize_t i = it->parent_stack_used; | |
| it->parent_stack_used = 0; | |
| /* bpo-31095: UnTrack is needed before calling any callbacks */ | |
| PyObject_GC_UnTrack(it); | |
| while (i--) | |
| Py_XDECREF(it->parent_stack[i].parent); | |
| PyMem_Free(it->parent_stack); | |
| Py_XDECREF(it->sought_tag); | |
| Py_XDECREF(it->root_element); | |
| tp->tp_free(it); | |
| Py_DECREF(tp); | |
| } | |
| static int | |
| elementiter_traverse(PyObject *op, visitproc visit, void *arg) | |
| { | |
| ElementIterObject *it = _ElementIter_CAST(op); | |
| Py_ssize_t i = it->parent_stack_used; | |
| while (i--) | |
| Py_VISIT(it->parent_stack[i].parent); | |
| Py_VISIT(it->root_element); | |
| Py_VISIT(it->sought_tag); | |
| Py_VISIT(Py_TYPE(it)); | |
| return 0; | |
| } | |
| /* Helper function for elementiter_next. Add a new parent to the parent stack. | |
| */ | |
| static int | |
| parent_stack_push_new(ElementIterObject *it, ElementObject *parent) | |
| { | |
| ParentLocator *item; | |
| if (it->parent_stack_used >= it->parent_stack_size) { | |
| Py_ssize_t new_size = it->parent_stack_size * 2; /* never overflow */ | |
| ParentLocator *parent_stack = it->parent_stack; | |
| PyMem_Resize(parent_stack, ParentLocator, new_size); | |
| if (parent_stack == NULL) | |
| return -1; | |
| it->parent_stack = parent_stack; | |
| it->parent_stack_size = new_size; | |
| } | |
| item = it->parent_stack + it->parent_stack_used++; | |
| item->parent = (ElementObject*)Py_NewRef(parent); | |
| item->child_index = 0; | |
| return 0; | |
| } | |
| static PyObject * | |
| elementiter_next(PyObject *op) | |
| { | |
| ElementIterObject *it = _ElementIter_CAST(op); | |
| /* Sub-element iterator. | |
| * | |
| * A short note on gettext: this function serves both the iter() and | |
| * itertext() methods to avoid code duplication. However, there are a few | |
| * small differences in the way these iterations work. Namely: | |
| * - itertext() only yields text from nodes that have it, and continues | |
| * iterating when a node doesn't have text (so it doesn't return any | |
| * node like iter()) | |
| * - itertext() also has to handle tail, after finishing with all the | |
| * children of a node. | |
| */ | |
| int rc; | |
| ElementObject *elem; | |
| PyObject *text; | |
| while (1) { | |
| /* Handle the case reached in the beginning and end of iteration, where | |
| * the parent stack is empty. If root_element is NULL and we're here, the | |
| * iterator is exhausted. | |
| */ | |
| if (!it->parent_stack_used) { | |
| if (!it->root_element) { | |
| PyErr_SetNone(PyExc_StopIteration); | |
| return NULL; | |
| } | |
| elem = it->root_element; /* steals a reference */ | |
| it->root_element = NULL; | |
| } | |
| else { | |
| /* See if there are children left to traverse in the current parent. If | |
| * yes, visit the next child. If not, pop the stack and try again. | |
| */ | |
| ParentLocator *item = &it->parent_stack[it->parent_stack_used - 1]; | |
| Py_ssize_t child_index = item->child_index; | |
| ElementObjectExtra *extra; | |
| elem = item->parent; | |
| extra = elem->extra; | |
| if (!extra || child_index >= extra->length) { | |
| it->parent_stack_used--; | |
| /* Note that extra condition on it->parent_stack_used here; | |
| * this is because itertext() is supposed to only return *inner* | |
| * text, not text following the element it began iteration with. | |
| */ | |
| if (it->gettext && it->parent_stack_used) { | |
| text = element_get_tail(elem); | |
| goto gettext; | |
| } | |
| Py_DECREF(elem); | |
| continue; | |
| } | |
| #ifndef NDEBUG | |
| PyTypeObject *tp = Py_TYPE(it); | |
| elementtreestate *st = get_elementtree_state_by_type(tp); | |
| assert(Element_Check(st, extra->children[child_index])); | |
| #endif | |
| elem = (ElementObject *)Py_NewRef(extra->children[child_index]); | |
| item->child_index++; | |
| } | |
| if (parent_stack_push_new(it, elem) < 0) { | |
| Py_DECREF(elem); | |
| PyErr_NoMemory(); | |
| return NULL; | |
| } | |
| if (it->gettext) { | |
| if (elem->tag != Py_None && !PyUnicode_Check(elem->tag)) { | |
| Py_DECREF(elem); | |
| continue; | |
| } | |
| text = element_get_text(elem); | |
| goto gettext; | |
| } | |
| if (it->sought_tag == Py_None) | |
| return (PyObject *)elem; | |
| rc = PyObject_RichCompareBool(elem->tag, it->sought_tag, Py_EQ); | |
| if (rc > 0) | |
| return (PyObject *)elem; | |
| Py_DECREF(elem); | |
| if (rc < 0) | |
| return NULL; | |
| continue; | |
| gettext: | |
| Py_DECREF(elem); | |
| if (!text) { | |
| return NULL; | |
| } | |
| if (text == Py_None) { | |
| Py_DECREF(text); | |
| } | |
| else { | |
| rc = PyObject_IsTrue(text); | |
| if (rc > 0) | |
| return text; | |
| Py_DECREF(text); | |
| if (rc < 0) | |
| return NULL; | |
| } | |
| } | |
| return NULL; | |
| } | |
| static PyType_Slot elementiter_slots[] = { | |
| {Py_tp_dealloc, elementiter_dealloc}, | |
| {Py_tp_traverse, elementiter_traverse}, | |
| {Py_tp_iter, PyObject_SelfIter}, | |
| {Py_tp_iternext, elementiter_next}, | |
| {0, NULL}, | |
| }; | |
| static PyType_Spec elementiter_spec = { | |
| /* Using the module's name since the pure-Python implementation does not | |
| have such a type. */ | |
| .name = "_elementtree._element_iterator", | |
| .basicsize = sizeof(ElementIterObject), | |
| .flags = (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | | |
| Py_TPFLAGS_IMMUTABLETYPE | Py_TPFLAGS_DISALLOW_INSTANTIATION), | |
| .slots = elementiter_slots, | |
| }; | |
| #define INIT_PARENT_STACK_SIZE 8 | |
| static PyObject * | |
| create_elementiter(elementtreestate *st, ElementObject *self, PyObject *tag, | |
| int gettext) | |
| { | |
| ElementIterObject *it; | |
| it = PyObject_GC_New(ElementIterObject, st->ElementIter_Type); | |
| if (!it) | |
| return NULL; | |
| it->sought_tag = Py_NewRef(tag); | |
| it->gettext = gettext; | |
| it->root_element = (ElementObject*)Py_NewRef(self); | |
| it->parent_stack = PyMem_New(ParentLocator, INIT_PARENT_STACK_SIZE); | |
| if (it->parent_stack == NULL) { | |
| Py_DECREF(it); | |
| PyErr_NoMemory(); | |
| return NULL; | |
| } | |
| it->parent_stack_used = 0; | |
| it->parent_stack_size = INIT_PARENT_STACK_SIZE; | |
| PyObject_GC_Track(it); | |
| return (PyObject *)it; | |
| } | |
| /* ==================================================================== */ | |
| /* the tree builder type */ | |
| typedef struct { | |
| PyObject_HEAD | |
| PyObject *root; /* root node (first created node) */ | |
| PyObject *this; /* current node */ | |
| PyObject *last; /* most recently created node */ | |
| PyObject *last_for_tail; /* most recently created node that takes a tail */ | |
| PyObject *data; /* data collector (string or list), or NULL */ | |
| PyObject *stack; /* element stack */ | |
| Py_ssize_t index; /* current stack size (0 means empty) */ | |
| PyObject *element_factory; | |
| PyObject *comment_factory; | |
| PyObject *pi_factory; | |
| /* element tracing */ | |
| char insert_comments; | |
| char insert_pis; | |
| elementtreestate *state; | |
| } TreeBuilderObject; | |
| #define _TreeBuilder_CAST(op) ((TreeBuilderObject *)(op)) | |
| #define TreeBuilder_CheckExact(st, op) Py_IS_TYPE((op), (st)->TreeBuilder_Type) | |
| /* -------------------------------------------------------------------- */ | |
| /* constructor and destructor */ | |
| static PyObject * | |
| treebuilder_new(PyTypeObject *type, PyObject *args, PyObject *kwds) | |
| { | |
| TreeBuilderObject *t = (TreeBuilderObject *)type->tp_alloc(type, 0); | |
| if (t != NULL) { | |
| t->root = NULL; | |
| t->this = Py_NewRef(Py_None); | |
| t->last = Py_NewRef(Py_None); | |
| t->data = NULL; | |
| t->element_factory = NULL; | |
| t->comment_factory = NULL; | |
| t->pi_factory = NULL; | |
| t->stack = PyList_New(20); | |
| if (!t->stack) { | |
| Py_DECREF(t->this); | |
| Py_DECREF(t->last); | |
| Py_DECREF((PyObject *) t); | |
| return NULL; | |
| } | |
| t->index = 0; | |
| t->insert_comments = t->insert_pis = 0; | |
| t->state = get_elementtree_state_by_type(type); | |
| } | |
| return (PyObject *)t; | |
| } | |
| /*[clinic input] | |
| _elementtree.TreeBuilder.__init__ | |
| element_factory: object = None | |
| * | |
| comment_factory: object = None | |
| pi_factory: object = None | |
| insert_comments: bool = False | |
| insert_pis: bool = False | |
| Generic element structure builder. | |
| This builder converts a sequence of start, data, and end method | |
| calls to a well-formed element structure. | |
| You can use this class to build an element structure using a custom | |
| XML parser, or a parser for some other XML-like format. | |
| *element_factory* is an optional element factory which is called | |
| to create new Element instances, as necessary. | |
| *comment_factory* is a factory to create comments to be used instead | |
| of the standard factory. If *insert_comments* is false (the | |
| default), comments will not be inserted into the tree. | |
| *pi_factory* is a factory to create processing instructions to be | |
| used instead of the standard factory. If *insert_pis* is false (the | |
| default), processing instructions will not be inserted into the tree. | |
| [clinic start generated code]*/ | |
| static int | |
| _elementtree_TreeBuilder___init___impl(TreeBuilderObject *self, | |
| PyObject *element_factory, | |
| PyObject *comment_factory, | |
| PyObject *pi_factory, | |
| int insert_comments, int insert_pis) | |
| /*[clinic end generated code: output=8571d4dcadfdf952 input=24fb5a482d93f8e4]*/ | |
| { | |
| if (element_factory != Py_None) { | |
| Py_XSETREF(self->element_factory, Py_NewRef(element_factory)); | |
| } else { | |
| Py_CLEAR(self->element_factory); | |
| } | |
| if (comment_factory == Py_None) { | |
| elementtreestate *st = self->state; | |
| comment_factory = st->comment_factory; | |
| } | |
| if (comment_factory) { | |
| Py_XSETREF(self->comment_factory, Py_NewRef(comment_factory)); | |
| self->insert_comments = insert_comments; | |
| } else { | |
| Py_CLEAR(self->comment_factory); | |
| self->insert_comments = 0; | |
| } | |
| if (pi_factory == Py_None) { | |
| elementtreestate *st = self->state; | |
| pi_factory = st->pi_factory; | |
| } | |
| if (pi_factory) { | |
| Py_XSETREF(self->pi_factory, Py_NewRef(pi_factory)); | |
| self->insert_pis = insert_pis; | |
| } else { | |
| Py_CLEAR(self->pi_factory); | |
| self->insert_pis = 0; | |
| } | |
| return 0; | |
| } | |
| static int | |
| treebuilder_gc_traverse(PyObject *op, visitproc visit, void *arg) | |
| { | |
| TreeBuilderObject *self = _TreeBuilder_CAST(op); | |
| Py_VISIT(Py_TYPE(self)); | |
| Py_VISIT(self->root); | |
| Py_VISIT(self->this); | |
| Py_VISIT(self->last); | |
| Py_VISIT(self->last_for_tail); | |
| Py_VISIT(self->data); | |
| Py_VISIT(self->stack); | |
| Py_VISIT(self->pi_factory); | |
| Py_VISIT(self->comment_factory); | |
| Py_VISIT(self->element_factory); | |
| return 0; | |
| } | |
| static int | |
| treebuilder_gc_clear(PyObject *op) | |
| { | |
| TreeBuilderObject *self = _TreeBuilder_CAST(op); | |
| Py_CLEAR(self->stack); | |
| Py_CLEAR(self->data); | |
| Py_CLEAR(self->last); | |
| Py_CLEAR(self->last_for_tail); | |
| Py_CLEAR(self->this); | |
| Py_CLEAR(self->pi_factory); | |
| Py_CLEAR(self->comment_factory); | |
| Py_CLEAR(self->element_factory); | |
| Py_CLEAR(self->root); | |
| return 0; | |
| } | |
| static void | |
| treebuilder_dealloc(PyObject *self) | |
| { | |
| PyTypeObject *tp = Py_TYPE(self); | |
| PyObject_GC_UnTrack(self); | |
| (void)treebuilder_gc_clear(self); | |
| tp->tp_free(self); | |
| Py_DECREF(tp); | |
| } | |
| /* -------------------------------------------------------------------- */ | |
| /* helpers for handling of arbitrary element-like objects */ | |
| /*[clinic input] | |
| @permit_long_summary | |
| _elementtree._set_factories | |
| comment_factory: object | |
| pi_factory: object | |
| / | |
| Change the factories used to create comments and processing instructions. | |
| For internal use only. | |
| [clinic start generated code]*/ | |
| static PyObject * | |
| _elementtree__set_factories_impl(PyObject *module, PyObject *comment_factory, | |
| PyObject *pi_factory) | |
| /*[clinic end generated code: output=813b408adee26535 input=0f415cb6b821f768]*/ | |
| { | |
| elementtreestate *st = get_elementtree_state(module); | |
| PyObject *old; | |
| if (!PyCallable_Check(comment_factory) && comment_factory != Py_None) { | |
| PyErr_Format(PyExc_TypeError, "Comment factory must be callable, not %.100s", | |
| Py_TYPE(comment_factory)->tp_name); | |
| return NULL; | |
| } | |
| if (!PyCallable_Check(pi_factory) && pi_factory != Py_None) { | |
| PyErr_Format(PyExc_TypeError, "PI factory must be callable, not %.100s", | |
| Py_TYPE(pi_factory)->tp_name); | |
| return NULL; | |
| } | |
| old = _PyTuple_FromPair( | |
| st->comment_factory ? st->comment_factory : Py_None, | |
| st->pi_factory ? st->pi_factory : Py_None); | |
| if (comment_factory == Py_None) { | |
| Py_CLEAR(st->comment_factory); | |
| } else { | |
| Py_XSETREF(st->comment_factory, Py_NewRef(comment_factory)); | |
| } | |
| if (pi_factory == Py_None) { | |
| Py_CLEAR(st->pi_factory); | |
| } else { | |
| Py_XSETREF(st->pi_factory, Py_NewRef(pi_factory)); | |
| } | |
| return old; | |
| } | |
| static int | |
| treebuilder_extend_element_text_or_tail(elementtreestate *st, PyObject *element, | |
| PyObject **data, PyObject **dest, | |
| PyObject *name) | |
| { | |
| /* Fast paths for the "almost always" cases. */ | |
| if (Element_CheckExact(st, element)) { | |
| PyObject *dest_obj = JOIN_OBJ(*dest); | |
| if (dest_obj == Py_None) { | |
| *dest = JOIN_SET(*data, PyList_CheckExact(*data)); | |
| *data = NULL; | |
| Py_DECREF(dest_obj); | |
| return 0; | |
| } | |
| else if (JOIN_GET(*dest)) { | |
| if (PyList_SetSlice(dest_obj, PY_SSIZE_T_MAX, PY_SSIZE_T_MAX, *data) < 0) { | |
| return -1; | |
| } | |
| Py_CLEAR(*data); | |
| return 0; | |
| } | |
| } | |
| /* Fallback for the non-Element / non-trivial cases. */ | |
| { | |
| int r; | |
| PyObject* joined; | |
| PyObject* previous = PyObject_GetAttr(element, name); | |
| if (!previous) | |
| return -1; | |
| joined = list_join(*data); | |
| if (!joined) { | |
| Py_DECREF(previous); | |
| return -1; | |
| } | |
| if (previous != Py_None) { | |
| PyObject *tmp = PyNumber_Add(previous, joined); | |
| Py_DECREF(joined); | |
| Py_DECREF(previous); | |
| if (!tmp) | |
| return -1; | |
| joined = tmp; | |
| } else { | |
| Py_DECREF(previous); | |
| } | |
| r = PyObject_SetAttr(element, name, joined); | |
| Py_DECREF(joined); | |
| if (r < 0) | |
| return -1; | |
| Py_CLEAR(*data); | |
| return 0; | |
| } | |
| } | |
| LOCAL(int) | |
| treebuilder_flush_data(TreeBuilderObject* self) | |
| { | |
| if (!self->data) { | |
| return 0; | |
| } | |
| elementtreestate *st = self->state; | |
| if (!self->last_for_tail) { | |
| PyObject *element = self->last; | |
| return treebuilder_extend_element_text_or_tail( | |
| st, element, &self->data, | |
| &((ElementObject *) element)->text, st->str_text); | |
| } | |
| else { | |
| PyObject *element = self->last_for_tail; | |
| return treebuilder_extend_element_text_or_tail( | |
| st, element, &self->data, | |
| &((ElementObject *) element)->tail, st->str_tail); | |
| } | |
| } | |
| static int | |
| treebuilder_add_subelement(elementtreestate *st, PyObject *element, | |
| PyObject *child) | |
| { | |
| if (Element_CheckExact(st, element)) { | |
| ElementObject *elem = (ElementObject *) element; | |
| return element_add_subelement(st, elem, child); | |
| } | |
| else { | |
| PyObject *res; | |
| res = PyObject_CallMethodOneArg(element, st->str_append, child); | |
| if (res == NULL) | |
| return -1; | |
| Py_DECREF(res); | |
| return 0; | |
| } | |
| } | |
| /* -------------------------------------------------------------------- */ | |
| /* handlers */ | |
| LOCAL(PyObject*) | |
| treebuilder_handle_start(TreeBuilderObject* self, PyObject* tag, | |
| PyObject* attrib) | |
| { | |
| PyObject* node; | |
| PyObject* this; | |
| elementtreestate *st = self->state; | |
| if (treebuilder_flush_data(self) < 0) { | |
| return NULL; | |
| } | |
| if (!self->element_factory) { | |
| node = create_new_element(st, tag, attrib); | |
| } | |
| else if (attrib == NULL) { | |
| attrib = PyDict_New(); | |
| if (!attrib) | |
| return NULL; | |
| node = PyObject_CallFunctionObjArgs(self->element_factory, | |
| tag, attrib, NULL); | |
| Py_DECREF(attrib); | |
| } | |
| else { | |
| node = PyObject_CallFunctionObjArgs(self->element_factory, | |
| tag, attrib, NULL); | |
| } | |
| if (!node) { | |
| return NULL; | |
| } | |
| this = self->this; | |
| Py_CLEAR(self->last_for_tail); | |
| if (this != Py_None) { | |
| if (treebuilder_add_subelement(st, this, node) < 0) { | |
| goto error; | |
| } | |
| } else { | |
| if (self->root) { | |
| PyErr_SetString( | |
| st->parseerror_obj, | |
| "multiple elements on top level" | |
| ); | |
| goto error; | |
| } | |
| self->root = Py_NewRef(node); | |
| } | |
| if (self->index < PyList_GET_SIZE(self->stack)) { | |
| if (PyList_SetItem(self->stack, self->index, this) < 0) | |
| goto error; | |
| Py_INCREF(this); | |
| } else { | |
| if (PyList_Append(self->stack, this) < 0) | |
| goto error; | |
| } | |
| self->index++; | |
| Py_SETREF(self->this, Py_NewRef(node)); | |
| Py_SETREF(self->last, Py_NewRef(node)); | |
| return node; | |
| error: | |
| Py_DECREF(node); | |
| return NULL; | |
| } | |
| LOCAL(PyObject*) | |
| treebuilder_handle_data(TreeBuilderObject* self, PyObject* data) | |
| { | |
| if (!self->data) { | |
| if (self->last == Py_None) { | |
| /* ignore calls to data before the first call to start */ | |
| Py_RETURN_NONE; | |
| } | |
| /* store the first item as is */ | |
| self->data = Py_NewRef(data); | |
| } else { | |
| /* more than one item; use a list to collect items */ | |
| if (PyList_CheckExact(self->data)) { | |
| if (PyList_Append(self->data, data) < 0) | |
| return NULL; | |
| } else { | |
| PyObject* list = PyList_New(2); | |
| if (!list) | |
| return NULL; | |
| PyList_SET_ITEM(list, 0, Py_NewRef(self->data)); | |
| PyList_SET_ITEM(list, 1, Py_NewRef(data)); | |
| Py_SETREF(self->data, list); | |
| } | |
| } | |
| Py_RETURN_NONE; | |
| } | |
| LOCAL(PyObject*) | |
| treebuilder_handle_end(TreeBuilderObject* self, PyObject* tag) | |
| { | |
| if (treebuilder_flush_data(self) < 0) { | |
| return NULL; | |
| } | |
| if (self->index == 0) { | |
| PyErr_SetString( | |
| PyExc_IndexError, | |
| "pop from empty stack" | |
| ); | |
| return NULL; | |
| } | |
| PyObject *last = self->last; | |
| PyObject *last_for_tail = self->last_for_tail; | |
| PyObject *this = self->this; | |
| self->last = Py_NewRef(this); | |
| self->last_for_tail = Py_NewRef(this); | |
| self->index--; | |
| self->this = Py_NewRef(PyList_GET_ITEM(self->stack, self->index)); | |
| Py_DECREF(last); | |
| Py_XDECREF(last_for_tail); | |
| return this; | |
| } | |
| LOCAL(PyObject*) | |
| treebuilder_handle_comment(TreeBuilderObject* self, PyObject* text) | |
| { | |
| PyObject* comment; | |
| PyObject* this; | |
| if (treebuilder_flush_data(self) < 0) { | |
| return NULL; | |
| } | |
| if (self->comment_factory) { | |
| comment = PyObject_CallOneArg(self->comment_factory, text); | |
| if (!comment) | |
| return NULL; | |
| this = self->this; | |
| if (self->insert_comments && this != Py_None) { | |
| if (treebuilder_add_subelement(self->state, this, comment) < 0) { | |
| goto error; | |
| } | |
| Py_XSETREF(self->last_for_tail, Py_NewRef(comment)); | |
| } | |
| } else { | |
| comment = Py_NewRef(text); | |
| } | |
| return comment; | |
| error: | |
| Py_DECREF(comment); | |
| return NULL; | |
| } | |
| LOCAL(PyObject*) | |
| treebuilder_handle_pi(TreeBuilderObject* self, PyObject* target, PyObject* text) | |
| { | |
| PyObject* pi; | |
| PyObject* this; | |
| if (treebuilder_flush_data(self) < 0) { | |
| return NULL; | |
| } | |
| if (self->pi_factory) { | |
| PyObject* args[2] = {target, text}; | |
| pi = PyObject_Vectorcall(self->pi_factory, args, 2, NULL); | |
| if (!pi) { | |
| return NULL; | |
| } | |
| this = self->this; | |
| if (self->insert_pis && this != Py_None) { | |
| if (treebuilder_add_subelement(self->state, this, pi) < 0) { | |
| goto error; | |
| } | |
| Py_XSETREF(self->last_for_tail, Py_NewRef(pi)); | |
| } | |
| } else { | |
| pi = _PyTuple_FromPair(target, text); | |
| if (!pi) { | |
| return NULL; | |
| } | |
| } | |
| return pi; | |
| error: | |
| Py_DECREF(pi); | |
| return NULL; | |
| } | |
| /* -------------------------------------------------------------------- */ | |
| /* methods (in alphabetical order) */ | |
| /*[clinic input] | |
| _elementtree.TreeBuilder.data | |
| data: object | |
| / | |
| Add text to current element. | |
| [clinic start generated code]*/ | |
| static PyObject * | |
| _elementtree_TreeBuilder_data_impl(TreeBuilderObject *self, PyObject *data) | |
| /*[clinic end generated code: output=dfa02b68f732b8c0 input=679b26864cecbde8]*/ | |
| { | |
| return treebuilder_handle_data(self, data); | |
| } | |
| /*[clinic input] | |
| _elementtree.TreeBuilder.end | |
| tag: object | |
| / | |
| Close and return current Element. | |
| *tag* is the element name. | |
| [clinic start generated code]*/ | |
| static PyObject * | |
| _elementtree_TreeBuilder_end_impl(TreeBuilderObject *self, PyObject *tag) | |
| /*[clinic end generated code: output=84cb6ca9008ec740 input=9d161338282e5fac]*/ | |
| { | |
| return treebuilder_handle_end(self, tag); | |
| } | |
| /*[clinic input] | |
| _elementtree.TreeBuilder.comment | |
| text: object | |
| / | |
| Create a comment using the comment_factory. | |
| *text* is the text of the comment. | |
| [clinic start generated code]*/ | |
| static PyObject * | |
| _elementtree_TreeBuilder_comment_impl(TreeBuilderObject *self, | |
| PyObject *text) | |
| /*[clinic end generated code: output=a555ef39027c3823 input=b1579b62bb9277e4]*/ | |
| { | |
| return treebuilder_handle_comment(self, text); | |
| } | |
| /*[clinic input] | |
| _elementtree.TreeBuilder.pi | |
| target: object | |
| text: object = None | |
| / | |
| Create a processing instruction using the pi_factory. | |
| *target* is the target name of the processing instruction. *text* is | |
| the data of the processing instruction, or ''. | |
| [clinic start generated code]*/ | |
| static PyObject * | |
| _elementtree_TreeBuilder_pi_impl(TreeBuilderObject *self, PyObject *target, | |
| PyObject *text) | |
| /*[clinic end generated code: output=21eb95ec9d04d1d9 input=160b939cc17e4121]*/ | |
| { | |
| return treebuilder_handle_pi(self, target, text); | |
| } | |
| LOCAL(PyObject*) | |
| treebuilder_done(TreeBuilderObject* self) | |
| { | |
| PyObject* res; | |
| /* FIXME: check stack size? */ | |
| if (self->root) | |
| res = self->root; | |
| else | |
| res = Py_None; | |
| return Py_NewRef(res); | |
| } | |
| /*[clinic input] | |
| _elementtree.TreeBuilder.close | |
| Flush builder buffers and return toplevel document Element. | |
| [clinic start generated code]*/ | |
| static PyObject * | |
| _elementtree_TreeBuilder_close_impl(TreeBuilderObject *self) | |
| /*[clinic end generated code: output=b441fee3202f61ee input=461e8391c6b73c5f]*/ | |
| { | |
| return treebuilder_done(self); | |
| } | |
| /*[clinic input] | |
| _elementtree.TreeBuilder.start | |
| tag: object | |
| attrs: object(subclass_of='&PyDict_Type') | |
| / | |
| Open new element and return it. | |
| *tag* is the element name, *attrs* is a dict containing element | |
| attributes. | |
| [clinic start generated code]*/ | |
| static PyObject * | |
| _elementtree_TreeBuilder_start_impl(TreeBuilderObject *self, PyObject *tag, | |
| PyObject *attrs) | |
| /*[clinic end generated code: output=e7e9dc2861349411 input=26cccb49c3b8b12f]*/ | |
| { | |
| return treebuilder_handle_start(self, tag, attrs); | |
| } | |
| /* ==================================================================== */ | |
| /* the expat interface */ | |
| #define EXPAT(st, func) ((st)->expat_capi->func) | |
| static XML_Memory_Handling_Suite ExpatMemoryHandler = { | |
| PyMem_Malloc, PyMem_Realloc, PyMem_Free}; | |
| typedef struct { | |
| PyObject_HEAD | |
| XML_Parser parser; | |
| PyObject *target; | |
| PyObject *entity; | |
| PyObject *names; | |
| 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; | |
| PyObject *handle_comment; | |
| PyObject *handle_pi; | |
| PyObject *handle_doctype; | |
| PyObject *handle_close; | |
| elementtreestate *state; | |
| PyObject *elementtree_module; | |
| } XMLParserObject; | |
| #define XMLParserObject_CAST(op) ((XMLParserObject *)(op)) | |
| /* helpers */ | |
| LOCAL(PyObject*) | |
| makeuniversal(XMLParserObject* self, const char* string) | |
| { | |
| /* convert a UTF-8 tag/attribute name from the expat parser | |
| to a universal name string */ | |
| Py_ssize_t size = (Py_ssize_t) strlen(string); | |
| PyObject* key; | |
| PyObject* value; | |
| /* look the 'raw' name up in the names dictionary */ | |
| key = PyBytes_FromStringAndSize(string, size); | |
| if (!key) | |
| return NULL; | |
| if (PyDict_GetItemRef(self->names, key, &value) != 0) { | |
| // The key exists or an error occurred | |
| Py_DECREF(key); | |
| return value; | |
| } | |
| /* new name. convert to universal name, and decode as | |
| necessary */ | |
| PyObject* tag; | |
| Py_ssize_t i; | |
| /* look for namespace separator */ | |
| for (i = 0; i < size; i++) | |
| if (string[i] == '}') | |
| break; | |
| if (i != size) { | |
| /* convert to universal name */ | |
| PyBytesWriter *writer = PyBytesWriter_Create(1 + size); | |
| if (writer == NULL) { | |
| Py_DECREF(key); | |
| return NULL; | |
| } | |
| char *p = PyBytesWriter_GetData(writer); | |
| p[0] = '{'; | |
| memcpy(p+1, string, size); | |
| size++; | |
| tag = PyBytesWriter_Finish(writer); | |
| if (tag == NULL) { | |
| Py_DECREF(key); | |
| return NULL; | |
| } | |
| } else { | |
| /* plain name; use key as tag */ | |
| tag = Py_NewRef(key); | |
| } | |
| /* decode universal name */ | |
| const char *p = PyBytes_AS_STRING(tag); | |
| value = PyUnicode_DecodeUTF8(p, size, "strict"); | |
| Py_DECREF(tag); | |
| if (!value) { | |
| Py_DECREF(key); | |
| return NULL; | |
| } | |
| /* add to names dictionary */ | |
| if (PyDict_SetItem(self->names, key, value) < 0) { | |
| Py_DECREF(key); | |
| Py_DECREF(value); | |
| return NULL; | |
| } | |
| Py_DECREF(key); | |
| return value; | |
| } | |
| /* Set the ParseError exception with the given parameters. | |
| * If message is not NULL, it's used as the error string. Otherwise, the | |
| * message string is the default for the given error_code. | |
| */ | |
| static void | |
| expat_set_error(elementtreestate *st, enum XML_Error error_code, | |
| Py_ssize_t line, Py_ssize_t column, const char *message) | |
| { | |
| PyObject *errmsg, *error, *position, *code; | |
| errmsg = PyUnicode_FromFormat("%s: line %zd, column %zd", | |
| message ? message : EXPAT(st, ErrorString)(error_code), | |
| line, column); | |
| if (errmsg == NULL) | |
| return; | |
| error = PyObject_CallOneArg(st->parseerror_obj, errmsg); | |
| Py_DECREF(errmsg); | |
| if (!error) | |
| return; | |
| /* Add code and position attributes */ | |
| code = PyLong_FromLong((long)error_code); | |
| if (!code) { | |
| Py_DECREF(error); | |
| return; | |
| } | |
| if (PyObject_SetAttrString(error, "code", code) == -1) { | |
| Py_DECREF(error); | |
| Py_DECREF(code); | |
| return; | |
| } | |
| Py_DECREF(code); | |
| position = Py_BuildValue("(nn)", line, column); | |
| if (!position) { | |
| Py_DECREF(error); | |
| return; | |
| } | |
| if (PyObject_SetAttrString(error, "position", position) == -1) { | |
| Py_DECREF(error); | |
| Py_DECREF(position); | |
| return; | |
| } | |
| Py_DECREF(position); | |
| PyErr_SetObject(st->parseerror_obj, error); | |
| Py_DECREF(error); | |
| } | |
| /* -------------------------------------------------------------------- */ | |
| /* handlers */ | |
| static void | |
| expat_default_handler(void *op, const XML_Char *data_in, int data_len) | |
| { | |
| XMLParserObject *self = XMLParserObject_CAST(op); | |
| PyObject *key; | |
| PyObject *value; | |
| PyObject *res; | |
| if (data_len < 2 || data_in[0] != '&') | |
| return; | |
| if (PyErr_Occurred()) | |
| return; | |
| key = PyUnicode_DecodeUTF8(data_in + 1, data_len - 2, "strict"); | |
| if (!key) | |
| return; | |
| if (PyDict_GetItemRef(self->entity, key, &value) < 0) { | |
| Py_DECREF(key); | |
| return; | |
| } | |
| Py_DECREF(key); | |
| elementtreestate *st = self->state; | |
| if (value) { | |
| if (TreeBuilder_CheckExact(st, self->target)) | |
| res = treebuilder_handle_data( | |
| (TreeBuilderObject*) self->target, value | |
| ); | |
| else if (self->handle_data) | |
| res = PyObject_CallOneArg(self->handle_data, value); | |
| else | |
| res = NULL; | |
| Py_DECREF(value); | |
| Py_XDECREF(res); | |
| } | |
| else { | |
| /* Report the first error, not the last */ | |
| char message[128] = "undefined entity "; | |
| strncat(message, data_in, data_len < 100?data_len:100); | |
| expat_set_error( | |
| st, | |
| XML_ERROR_UNDEFINED_ENTITY, | |
| EXPAT(st, GetErrorLineNumber)(self->parser), | |
| EXPAT(st, GetErrorColumnNumber)(self->parser), | |
| message | |
| ); | |
| } | |
| } | |
| /* 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) | |
| { | |
| XMLParserObject *self = XMLParserObject_CAST(op); | |
| PyObject *res; | |
| PyObject *tag; | |
| PyObject *attrib; | |
| int ok; | |
| if (PyErr_Occurred()) | |
| return; | |
| /* tag name */ | |
| tag = makeuniversal(self, tag_in); | |
| if (!tag) | |
| return; /* parser will look for errors */ | |
| /* attributes */ | |
| if (attrib_in[0]) { | |
| attrib = PyDict_New(); | |
| if (!attrib) { | |
| Py_DECREF(tag); | |
| return; | |
| } | |
| while (attrib_in[0] && attrib_in[1]) { | |
| PyObject *key = makeuniversal(self, attrib_in[0]); | |
| if (key == NULL) { | |
| Py_DECREF(attrib); | |
| Py_DECREF(tag); | |
| return; | |
| } | |
| PyObject *value = PyUnicode_DecodeUTF8(attrib_in[1], strlen(attrib_in[1]), "strict"); | |
| if (value == NULL) { | |
| Py_DECREF(key); | |
| Py_DECREF(attrib); | |
| Py_DECREF(tag); | |
| return; | |
| } | |
| ok = PyDict_SetItem(attrib, key, value); | |
| Py_DECREF(value); | |
| Py_DECREF(key); | |
| if (ok < 0) { | |
| Py_DECREF(attrib); | |
| Py_DECREF(tag); | |
| return; | |
| } | |
| attrib_in += 2; | |
| } | |
| } else { | |
| attrib = NULL; | |
| } | |
| elementtreestate *st = self->state; | |
| if (TreeBuilder_CheckExact(st, self->target)) { | |
| /* shortcut */ | |
| res = treebuilder_handle_start((TreeBuilderObject*) self->target, | |
| tag, attrib); | |
| } | |
| else if (self->handle_start) { | |
| if (attrib == NULL) { | |
| attrib = PyDict_New(); | |
| if (!attrib) { | |
| Py_DECREF(tag); | |
| return; | |
| } | |
| } | |
| res = PyObject_CallFunctionObjArgs(self->handle_start, | |
| tag, attrib, NULL); | |
| } else | |
| res = NULL; | |
| Py_DECREF(tag); | |
| Py_XDECREF(attrib); | |
| if (res != NULL) { | |
| (void)xmlparser_append_event(self, self->start_event_obj, res); | |
| Py_DECREF(res); | |
| } | |
| } | |
| static void | |
| expat_data_handler(void *op, const XML_Char *data_in, | |
| int data_len) | |
| { | |
| XMLParserObject *self = XMLParserObject_CAST(op); | |
| PyObject *data; | |
| PyObject *res; | |
| if (PyErr_Occurred()) | |
| return; | |
| data = PyUnicode_DecodeUTF8(data_in, data_len, "strict"); | |
| if (!data) | |
| return; /* parser will look for errors */ | |
| elementtreestate *st = self->state; | |
| if (TreeBuilder_CheckExact(st, self->target)) | |
| /* shortcut */ | |
| res = treebuilder_handle_data((TreeBuilderObject*) self->target, data); | |
| else if (self->handle_data) | |
| res = PyObject_CallOneArg(self->handle_data, data); | |
| else | |
| res = NULL; | |
| Py_DECREF(data); | |
| Py_XDECREF(res); | |
| } | |
| static void | |
| expat_end_handler(void *op, const XML_Char *tag_in) | |
| { | |
| XMLParserObject *self = XMLParserObject_CAST(op); | |
| PyObject *tag; | |
| PyObject *res = NULL; | |
| if (PyErr_Occurred()) | |
| return; | |
| elementtreestate *st = self->state; | |
| if (TreeBuilder_CheckExact(st, self->target)) | |
| /* shortcut */ | |
| /* the standard tree builder doesn't look at the end tag */ | |
| res = treebuilder_handle_end( | |
| (TreeBuilderObject*) self->target, Py_None | |
| ); | |
| else if (self->handle_end) { | |
| tag = makeuniversal(self, tag_in); | |
| if (tag) { | |
| res = PyObject_CallOneArg(self->handle_end, tag); | |
| Py_DECREF(tag); | |
| } | |
| } | |
| if (res != NULL) { | |
| (void)xmlparser_append_event(self, self->end_event_obj, res); | |
| Py_DECREF(res); | |
| } | |
| } | |
| static void | |
| expat_start_ns_handler(void *op, const XML_Char *prefix_in, | |
| const XML_Char *uri_in) | |
| { | |
| XMLParserObject *self = XMLParserObject_CAST(op); | |
| PyObject *res = NULL; | |
| PyObject *uri; | |
| PyObject *prefix; | |
| if (PyErr_Occurred()) | |
| return; | |
| if (!uri_in) | |
| uri_in = ""; | |
| if (!prefix_in) | |
| prefix_in = ""; | |
| if (self->handle_start_ns == NULL && self->start_ns_event_obj == NULL) { | |
| 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); | |
| } | |
| else { | |
| /* the target does not implement .start_ns(), report the pair */ | |
| res = _PyTuple_FromPair(prefix, uri); | |
| } | |
| Py_DECREF(uri); | |
| Py_DECREF(prefix); | |
| if (res != NULL) { | |
| (void)xmlparser_append_event(self, self->start_ns_event_obj, res); | |
| Py_DECREF(res); | |
| } | |
| } | |
| static void | |
| expat_end_ns_handler(void *op, const XML_Char *prefix_in) | |
| { | |
| XMLParserObject *self = XMLParserObject_CAST(op); | |
| PyObject *res = NULL; | |
| PyObject *prefix; | |
| if (PyErr_Occurred()) | |
| return; | |
| if (!prefix_in) | |
| prefix_in = ""; | |
| if (self->handle_end_ns) { | |
| prefix = PyUnicode_DecodeUTF8(prefix_in, strlen(prefix_in), "strict"); | |
| if (!prefix) | |
| return; | |
| 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); | |
| } | |
| if (res != NULL) { | |
| (void)xmlparser_append_event(self, self->end_ns_event_obj, res); | |
| Py_DECREF(res); | |
| } | |
| } | |
| static void | |
| expat_comment_handler(void *op, const XML_Char *comment_in) | |
| { | |
| XMLParserObject *self = XMLParserObject_CAST(op); | |
| PyObject *comment; | |
| PyObject *res; | |
| if (PyErr_Occurred()) | |
| return; | |
| elementtreestate *st = self->state; | |
| if (TreeBuilder_CheckExact(st, self->target)) { | |
| /* shortcut */ | |
| TreeBuilderObject *target = (TreeBuilderObject*) self->target; | |
| comment = PyUnicode_DecodeUTF8(comment_in, strlen(comment_in), "strict"); | |
| if (!comment) | |
| return; /* parser will look for errors */ | |
| res = treebuilder_handle_comment(target, comment); | |
| 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_DECREF(comment); | |
| if (res != NULL) { | |
| (void)xmlparser_append_event(self, self->comment_event_obj, res); | |
| Py_DECREF(res); | |
| } | |
| } | |
| } | |
| static void | |
| expat_start_doctype_handler(void *op, | |
| const XML_Char *doctype_name, | |
| const XML_Char *sysid, | |
| const XML_Char *pubid, | |
| int has_internal_subset) | |
| { | |
| XMLParserObject *self = XMLParserObject_CAST(op); | |
| PyObject *doctype_name_obj, *sysid_obj, *pubid_obj; | |
| PyObject *res; | |
| if (PyErr_Occurred()) | |
| return; | |
| doctype_name_obj = makeuniversal(self, doctype_name); | |
| if (!doctype_name_obj) | |
| return; | |
| if (sysid) { | |
| sysid_obj = makeuniversal(self, sysid); | |
| if (!sysid_obj) { | |
| Py_DECREF(doctype_name_obj); | |
| return; | |
| } | |
| } else { | |
| sysid_obj = Py_NewRef(Py_None); | |
| } | |
| if (pubid) { | |
| pubid_obj = makeuniversal(self, pubid); | |
| if (!pubid_obj) { | |
| Py_DECREF(doctype_name_obj); | |
| Py_DECREF(sysid_obj); | |
| return; | |
| } | |
| } else { | |
| pubid_obj = Py_NewRef(Py_None); | |
| } | |
| elementtreestate *st = self->state; | |
| /* If the target has a handler for doctype, call it. */ | |
| if (self->handle_doctype) { | |
| res = PyObject_CallFunctionObjArgs(self->handle_doctype, | |
| doctype_name_obj, pubid_obj, | |
| sysid_obj, NULL); | |
| Py_XDECREF(res); | |
| } | |
| else if (PyObject_HasAttrWithError((PyObject *)self, st->str_doctype) > 0) { | |
| (void)PyErr_WarnEx(PyExc_RuntimeWarning, | |
| "The doctype() method of XMLParser is ignored. " | |
| "Define doctype() method on the TreeBuilder target.", | |
| 1); | |
| } | |
| Py_DECREF(doctype_name_obj); | |
| Py_DECREF(pubid_obj); | |
| Py_DECREF(sysid_obj); | |
| } | |
| static void | |
| expat_pi_handler(void *op, const XML_Char *target_in, | |
| const XML_Char *data_in) | |
| { | |
| XMLParserObject *self = XMLParserObject_CAST(op); | |
| PyObject *pi_target; | |
| PyObject *data; | |
| PyObject *res; | |
| if (PyErr_Occurred()) | |
| return; | |
| elementtreestate *st = self->state; | |
| if (TreeBuilder_CheckExact(st, self->target)) { | |
| /* shortcut */ | |
| TreeBuilderObject *target = (TreeBuilderObject*) self->target; | |
| if (self->pi_event_obj || target->insert_pis) { | |
| pi_target = PyUnicode_DecodeUTF8(target_in, strlen(target_in), "strict"); | |
| if (!pi_target) | |
| goto error; | |
| data = PyUnicode_DecodeUTF8(data_in, strlen(data_in), "strict"); | |
| if (!data) | |
| goto error; | |
| res = treebuilder_handle_pi(target, pi_target, data); | |
| 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"); | |
| if (!pi_target) | |
| goto error; | |
| data = PyUnicode_DecodeUTF8(data_in, strlen(data_in), "strict"); | |
| if (!data) | |
| goto error; | |
| PyObject *args[2] = {pi_target, data}; | |
| res = PyObject_Vectorcall(self->handle_pi, args, 2, NULL); | |
| Py_DECREF(data); | |
| Py_DECREF(pi_target); | |
| if (res != NULL) { | |
| (void)xmlparser_append_event(self, self->pi_event_obj, res); | |
| Py_DECREF(res); | |
| } | |
| } | |
| return; | |
| error: | |
| Py_XDECREF(pi_target); | |
| return; | |
| } | |
| /* -------------------------------------------------------------------- */ | |
| static PyObject * | |
| xmlparser_new(PyTypeObject *type, PyObject *args, PyObject *kwds) | |
| { | |
| XMLParserObject *self = (XMLParserObject *)type->tp_alloc(type, 0); | |
| if (self) { | |
| self->parser = NULL; | |
| self->target = self->entity = self->names = NULL; | |
| self->handle_start_ns = self->handle_end_ns = NULL; | |
| 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); | |
| // See gh-111784 for explanation why is reference to module needed here. | |
| self->state = get_elementtree_state(self->elementtree_module); | |
| } | |
| return (PyObject *)self; | |
| } | |
| static int | |
| ignore_attribute_error(PyObject *value) | |
| { | |
| if (value == NULL) { | |
| if (!PyErr_ExceptionMatches(PyExc_AttributeError)) { | |
| return -1; | |
| } | |
| PyErr_Clear(); | |
| } | |
| return 0; | |
| } | |
| /*[clinic input] | |
| _elementtree.XMLParser.__init__ | |
| * | |
| target: object = None | |
| encoding: str(accept={str, NoneType}) = None | |
| Element structure builder for XML source data based on the expat parser. | |
| *target* is an optional target object which defaults to an instance | |
| of the standard TreeBuilder class, *encoding* is an optional encoding | |
| string which if given, overrides the encoding specified in the XML | |
| file: http://www.iana.org/assignments/character-sets | |
| [clinic start generated code]*/ | |
| static int | |
| _elementtree_XMLParser___init___impl(XMLParserObject *self, PyObject *target, | |
| const char *encoding) | |
| /*[clinic end generated code: output=3ae45ec6cdf344e4 input=a77eb075d276d487]*/ | |
| { | |
| self->entity = PyDict_New(); | |
| if (!self->entity) | |
| return -1; | |
| self->names = PyDict_New(); | |
| if (!self->names) { | |
| Py_CLEAR(self->entity); | |
| return -1; | |
| } | |
| elementtreestate *st = self->state; | |
| self->parser = EXPAT(st, ParserCreate_MM)(encoding, &ExpatMemoryHandler, "}"); | |
| if (!self->parser) { | |
| Py_CLEAR(self->entity); | |
| Py_CLEAR(self->names); | |
| PyErr_NoMemory(); | |
| return -1; | |
| } | |
| // Prefer 16-byte entropy, only expat >= 2.8.0. See gh-149018 | |
| if (EXPAT(st, SetHashSalt16Bytes) != NULL) { | |
| EXPAT(st, SetHashSalt16Bytes)(self->parser, | |
| _Py_HashSecret.expat.hashsalt16); | |
| } | |
| else if (EXPAT(st, SetHashSalt) != NULL) { | |
| EXPAT(st, SetHashSalt)(self->parser, | |
| (unsigned long)_Py_HashSecret.expat.hashsalt); | |
| } | |
| if (target != Py_None) { | |
| Py_INCREF(target); | |
| } else { | |
| target = treebuilder_new(st->TreeBuilder_Type, NULL, NULL); | |
| if (!target) { | |
| Py_CLEAR(self->entity); | |
| Py_CLEAR(self->names); | |
| return -1; | |
| } | |
| } | |
| self->target = target; | |
| self->handle_start_ns = PyObject_GetAttrString(target, "start_ns"); | |
| if (ignore_attribute_error(self->handle_start_ns)) { | |
| return -1; | |
| } | |
| self->handle_end_ns = PyObject_GetAttrString(target, "end_ns"); | |
| if (ignore_attribute_error(self->handle_end_ns)) { | |
| return -1; | |
| } | |
| self->handle_start = PyObject_GetAttrString(target, "start"); | |
| if (ignore_attribute_error(self->handle_start)) { | |
| return -1; | |
| } | |
| self->handle_data = PyObject_GetAttrString(target, "data"); | |
| if (ignore_attribute_error(self->handle_data)) { | |
| return -1; | |
| } | |
| self->handle_end = PyObject_GetAttrString(target, "end"); | |
| if (ignore_attribute_error(self->handle_end)) { | |
| return -1; | |
| } | |
| self->handle_comment = PyObject_GetAttrString(target, "comment"); | |
| if (ignore_attribute_error(self->handle_comment)) { | |
| return -1; | |
| } | |
| self->handle_pi = PyObject_GetAttrString(target, "pi"); | |
| if (ignore_attribute_error(self->handle_pi)) { | |
| return -1; | |
| } | |
| self->handle_close = PyObject_GetAttrString(target, "close"); | |
| if (ignore_attribute_error(self->handle_close)) { | |
| return -1; | |
| } | |
| self->handle_doctype = PyObject_GetAttrString(target, "doctype"); | |
| if (ignore_attribute_error(self->handle_doctype)) { | |
| return -1; | |
| } | |
| /* configure parser */ | |
| EXPAT(st, SetUserData)(self->parser, self); | |
| if (self->handle_start_ns || self->handle_end_ns) | |
| EXPAT(st, SetNamespaceDeclHandler)( | |
| self->parser, | |
| (XML_StartNamespaceDeclHandler) expat_start_ns_handler, | |
| (XML_EndNamespaceDeclHandler) expat_end_ns_handler | |
| ); | |
| EXPAT(st, SetElementHandler)( | |
| self->parser, | |
| (XML_StartElementHandler) expat_start_handler, | |
| (XML_EndElementHandler) expat_end_handler | |
| ); | |
| EXPAT(st, SetDefaultHandlerExpand)( | |
| self->parser, | |
| (XML_DefaultHandler) expat_default_handler | |
| ); | |
| EXPAT(st, SetCharacterDataHandler)( | |
| self->parser, | |
| (XML_CharacterDataHandler) expat_data_handler | |
| ); | |
| if (self->handle_comment) | |
| EXPAT(st, SetCommentHandler)( | |
| self->parser, | |
| (XML_CommentHandler) expat_comment_handler | |
| ); | |
| if (self->handle_pi) | |
| EXPAT(st, SetProcessingInstructionHandler)( | |
| self->parser, | |
| (XML_ProcessingInstructionHandler) expat_pi_handler | |
| ); | |
| EXPAT(st, SetStartDoctypeDeclHandler)( | |
| self->parser, | |
| (XML_StartDoctypeDeclHandler) expat_start_doctype_handler | |
| ); | |
| EXPAT(st, SetUnknownEncodingHandler)( | |
| self->parser, | |
| EXPAT(st, DefaultUnknownEncodingHandler), NULL | |
| ); | |
| return 0; | |
| } | |
| static int | |
| xmlparser_gc_traverse(PyObject *op, visitproc visit, void *arg) | |
| { | |
| XMLParserObject *self = XMLParserObject_CAST(op); | |
| Py_VISIT(Py_TYPE(self)); | |
| Py_VISIT(self->handle_close); | |
| Py_VISIT(self->handle_pi); | |
| Py_VISIT(self->handle_comment); | |
| Py_VISIT(self->handle_end); | |
| Py_VISIT(self->handle_data); | |
| Py_VISIT(self->handle_start); | |
| 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); | |
| Py_VISIT(self->names); | |
| return 0; | |
| } | |
| static int | |
| xmlparser_gc_clear(PyObject *op) | |
| { | |
| XMLParserObject *self = XMLParserObject_CAST(op); | |
| elementtreestate *st = self->state; | |
| if (self->parser != NULL) { | |
| XML_Parser parser = self->parser; | |
| self->parser = NULL; | |
| EXPAT(st, ParserFree)(parser); | |
| } | |
| Py_CLEAR(self->elementtree_module); | |
| Py_CLEAR(self->handle_close); | |
| Py_CLEAR(self->handle_pi); | |
| Py_CLEAR(self->handle_comment); | |
| Py_CLEAR(self->handle_end); | |
| Py_CLEAR(self->handle_data); | |
| Py_CLEAR(self->handle_start); | |
| 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); | |
| Py_CLEAR(self->names); | |
| return 0; | |
| } | |
| static void | |
| xmlparser_dealloc(PyObject *self) | |
| { | |
| PyTypeObject *tp = Py_TYPE(self); | |
| PyObject_GC_UnTrack(self); | |
| (void)xmlparser_gc_clear(self); | |
| tp->tp_free(self); | |
| Py_DECREF(tp); | |
| } | |
| Py_LOCAL_INLINE(int) | |
| _check_xmlparser(XMLParserObject* self) | |
| { | |
| if (self->target == NULL) { | |
| PyErr_SetString(PyExc_ValueError, | |
| "XMLParser.__init__() wasn't called"); | |
| return 0; | |
| } | |
| return 1; | |
| } | |
| LOCAL(PyObject*) | |
| expat_parse(elementtreestate *st, XMLParserObject *self, const char *data, | |
| int data_len, int final) | |
| { | |
| int ok; | |
| assert(!PyErr_Occurred()); | |
| ok = EXPAT(st, Parse)(self->parser, data, data_len, final); | |
| if (PyErr_Occurred()) | |
| return NULL; | |
| if (!ok) { | |
| expat_set_error( | |
| st, | |
| EXPAT(st, GetErrorCode)(self->parser), | |
| EXPAT(st, GetErrorLineNumber)(self->parser), | |
| EXPAT(st, GetErrorColumnNumber)(self->parser), | |
| NULL | |
| ); | |
| return NULL; | |
| } | |
| Py_RETURN_NONE; | |
| } | |
| /* Expat takes the length as an int, feed larger data in chunks. */ | |
| #define MAX_CHUNK_SIZE (1 << 20) | |
| LOCAL(PyObject*) | |
| expat_parse_large(elementtreestate *st, XMLParserObject *self, | |
| const char *data, Py_ssize_t data_len, int final) | |
| { | |
| static_assert(MAX_CHUNK_SIZE <= INT_MAX, | |
| "MAX_CHUNK_SIZE is larger than INT_MAX"); | |
| while (data_len > MAX_CHUNK_SIZE) { | |
| PyObject *res = expat_parse(st, self, data, MAX_CHUNK_SIZE, 0); | |
| if (res == NULL) { | |
| return NULL; | |
| } | |
| Py_DECREF(res); | |
| data += MAX_CHUNK_SIZE; | |
| data_len -= MAX_CHUNK_SIZE; | |
| } | |
| return expat_parse(st, self, data, (int)data_len, final); | |
| } | |
| /*[clinic input] | |
| _elementtree.XMLParser.close | |
| Finish feeding data to parser and return element structure. | |
| [clinic start generated code]*/ | |
| static PyObject * | |
| _elementtree_XMLParser_close_impl(XMLParserObject *self) | |
| /*[clinic end generated code: output=d68d375dd23bc7fb input=177603f3353e644f]*/ | |
| { | |
| /* end feeding data to parser */ | |
| PyObject* res; | |
| if (!_check_xmlparser(self)) { | |
| return NULL; | |
| } | |
| elementtreestate *st = self->state; | |
| res = expat_parse(st, self, "", 0, 1); | |
| if (!res) | |
| return NULL; | |
| if (TreeBuilder_CheckExact(st, self->target)) { | |
| Py_DECREF(res); | |
| return treebuilder_done((TreeBuilderObject*) self->target); | |
| } | |
| else if (self->handle_close) { | |
| Py_DECREF(res); | |
| return PyObject_CallNoArgs(self->handle_close); | |
| } | |
| else { | |
| return res; | |
| } | |
| } | |
| /*[clinic input] | |
| _elementtree.XMLParser.flush | |
| [clinic start generated code]*/ | |
| static PyObject * | |
| _elementtree_XMLParser_flush_impl(XMLParserObject *self) | |
| /*[clinic end generated code: output=42fdb8795ca24509 input=effbecdb28715949]*/ | |
| { | |
| if (!_check_xmlparser(self)) { | |
| return NULL; | |
| } | |
| elementtreestate *st = self->state; | |
| if (EXPAT(st, SetReparseDeferralEnabled) == NULL) { | |
| Py_RETURN_NONE; | |
| } | |
| // NOTE: The Expat parser in the C implementation of ElementTree is not | |
| // exposed to the outside; as a result we known that reparse deferral | |
| // is currently enabled, or we would not even have access to function | |
| // XML_SetReparseDeferralEnabled in the first place (which we checked | |
| // for, a few lines up). | |
| EXPAT(st, SetReparseDeferralEnabled)(self->parser, XML_FALSE); | |
| PyObject *res = expat_parse(st, self, "", 0, XML_FALSE); | |
| EXPAT(st, SetReparseDeferralEnabled)(self->parser, XML_TRUE); | |
| return res; | |
| } | |
| /*[clinic input] | |
| _elementtree.XMLParser.feed | |
| data: object | |
| / | |
| Feed encoded data to parser. | |
| [clinic start generated code]*/ | |
| static PyObject * | |
| _elementtree_XMLParser_feed_impl(XMLParserObject *self, PyObject *data) | |
| /*[clinic end generated code: output=503e6fbf1adf17ab input=9432a189100bc488]*/ | |
| { | |
| /* feed data to parser */ | |
| if (!_check_xmlparser(self)) { | |
| return NULL; | |
| } | |
| elementtreestate *st = self->state; | |
| if (PyUnicode_Check(data)) { | |
| Py_ssize_t data_len; | |
| const char *data_ptr = PyUnicode_AsUTF8AndSize(data, &data_len); | |
| if (data_ptr == NULL) | |
| return NULL; | |
| /* Explicitly set UTF-8 encoding. Return code ignored. */ | |
| (void)EXPAT(st, SetEncoding)(self->parser, "utf-8"); | |
| return expat_parse_large(st, self, data_ptr, data_len, 0); | |
| } | |
| else { | |
| Py_buffer view; | |
| PyObject *res; | |
| if (PyObject_GetBuffer(data, &view, PyBUF_SIMPLE) < 0) | |
| return NULL; | |
| res = expat_parse_large(st, self, view.buf, view.len, 0); | |
| PyBuffer_Release(&view); | |
| return res; | |
| } | |
| } | |
| /*[clinic input] | |
| _elementtree.XMLParser._parse_whole | |
| file: object | |
| / | |
| [clinic start generated code]*/ | |
| static PyObject * | |
| _elementtree_XMLParser__parse_whole_impl(XMLParserObject *self, | |
| PyObject *file) | |
| /*[clinic end generated code: output=60718a4e63d237d2 input=19ecc893b6f3e752]*/ | |
| { | |
| /* (internal) parse the whole input, until end of stream */ | |
| PyObject* reader; | |
| PyObject* buffer; | |
| PyObject* temp; | |
| PyObject* res; | |
| if (!_check_xmlparser(self)) { | |
| return NULL; | |
| } | |
| reader = PyObject_GetAttrString(file, "read"); | |
| if (!reader) | |
| return NULL; | |
| /* read from open file object */ | |
| elementtreestate *st = self->state; | |
| int first = 1; | |
| for (;;) { | |
| buffer = PyObject_CallFunction(reader, "i", 64*1024); | |
| if (!buffer) { | |
| /* read failed (e.g. due to KeyboardInterrupt) */ | |
| Py_DECREF(reader); | |
| return NULL; | |
| } | |
| if (PyUnicode_CheckExact(buffer)) { | |
| /* A unicode object is encoded into bytes using UTF-8 */ | |
| if (PyUnicode_GET_LENGTH(buffer) == 0) { | |
| Py_DECREF(buffer); | |
| break; | |
| } | |
| if (first) { | |
| /* The text is already decoded, the encoding declared in the | |
| document does not apply to it. Return code ignored. */ | |
| (void)EXPAT(st, SetEncoding)(self->parser, "utf-8"); | |
| } | |
| temp = PyUnicode_AsEncodedString(buffer, "utf-8", "surrogatepass"); | |
| Py_DECREF(buffer); | |
| if (!temp) { | |
| /* Propagate exception from PyUnicode_AsEncodedString */ | |
| Py_DECREF(reader); | |
| return NULL; | |
| } | |
| buffer = temp; | |
| } | |
| else if (!PyBytes_CheckExact(buffer) || PyBytes_GET_SIZE(buffer) == 0) { | |
| Py_DECREF(buffer); | |
| break; | |
| } | |
| res = expat_parse_large( | |
| st, self, PyBytes_AS_STRING(buffer), PyBytes_GET_SIZE(buffer), | |
| 0); | |
| first = 0; | |
| Py_DECREF(buffer); | |
| if (!res) { | |
| Py_DECREF(reader); | |
| return NULL; | |
| } | |
| Py_DECREF(res); | |
| } | |
| Py_DECREF(reader); | |
| res = expat_parse(st, self, "", 0, 1); | |
| if (res && TreeBuilder_CheckExact(st, self->target)) { | |
| Py_DECREF(res); | |
| return treebuilder_done((TreeBuilderObject*) self->target); | |
| } | |
| return res; | |
| } | |
| /*[clinic input] | |
| _elementtree.XMLParser._setevents | |
| events_queue: object | |
| events_to_report: object = None | |
| / | |
| [clinic start generated code]*/ | |
| static PyObject * | |
| _elementtree_XMLParser__setevents_impl(XMLParserObject *self, | |
| PyObject *events_queue, | |
| PyObject *events_to_report) | |
| /*[clinic end generated code: output=1440092922b13ed1 input=abf90830a1c3b0fc]*/ | |
| { | |
| /* activate element event reporting */ | |
| Py_ssize_t i; | |
| PyObject *events_append, *events_seq; | |
| if (!_check_xmlparser(self)) { | |
| return NULL; | |
| } | |
| elementtreestate *st = self->state; | |
| events_append = PyObject_GetAttrString(events_queue, "append"); | |
| if (events_append == NULL) | |
| return NULL; | |
| Py_XSETREF(self->events_append, events_append); | |
| /* clear out existing events */ | |
| 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 */ | |
| self->end_event_obj = PyUnicode_FromString("end"); | |
| Py_RETURN_NONE; | |
| } | |
| if (!(events_seq = PySequence_Fast(events_to_report, | |
| "events must be a sequence"))) { | |
| return NULL; | |
| } | |
| for (i = 0; i < PySequence_Fast_GET_SIZE(events_seq); ++i) { | |
| PyObject *event_name_obj = PySequence_Fast_GET_ITEM(events_seq, i); | |
| if (!PyUnicode_Check(event_name_obj)) { | |
| goto unknown_event; | |
| } | |
| const char *event_name = PyUnicode_AsUTF8(event_name_obj); | |
| if (event_name == NULL) { | |
| Py_DECREF(events_seq); | |
| return NULL; | |
| } | |
| /* the target must implement the method of the event, | |
| except for the namespace events */ | |
| PyObject *handler = Py_None; | |
| if (strcmp(event_name, "start") == 0) { | |
| handler = self->handle_start; | |
| } else if (strcmp(event_name, "end") == 0) { | |
| handler = self->handle_end; | |
| } else if (strcmp(event_name, "comment") == 0) { | |
| handler = self->handle_comment; | |
| } else if (strcmp(event_name, "pi") == 0) { | |
| handler = self->handle_pi; | |
| } | |
| if (handler == NULL) { | |
| PyErr_Format(PyExc_TypeError, | |
| "the target does not support %R events", | |
| event_name_obj); | |
| Py_DECREF(events_seq); | |
| return NULL; | |
| } | |
| if (strcmp(event_name, "start") == 0) { | |
| Py_XSETREF(self->start_event_obj, Py_NewRef(event_name_obj)); | |
| } else if (strcmp(event_name, "end") == 0) { | |
| Py_XSETREF(self->end_event_obj, Py_NewRef(event_name_obj)); | |
| } else if (strcmp(event_name, "start-ns") == 0) { | |
| 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(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(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(self->pi_event_obj, Py_NewRef(event_name_obj)); | |
| EXPAT(st, SetProcessingInstructionHandler)( | |
| self->parser, | |
| (XML_ProcessingInstructionHandler) expat_pi_handler | |
| ); | |
| } else { | |
| unknown_event: | |
| PyErr_Format(PyExc_ValueError, "unknown event %R", event_name_obj); | |
| Py_DECREF(events_seq); | |
| return NULL; | |
| } | |
| } | |
| Py_DECREF(events_seq); | |
| Py_RETURN_NONE; | |
| } | |
| static PyMemberDef xmlparser_members[] = { | |
| {"entity", _Py_T_OBJECT, offsetof(XMLParserObject, entity), Py_READONLY, NULL}, | |
| {"target", _Py_T_OBJECT, offsetof(XMLParserObject, target), Py_READONLY, NULL}, | |
| {NULL} | |
| }; | |
| static PyObject* | |
| xmlparser_version_getter(PyObject *op, void *closure) | |
| { | |
| return PyUnicode_FromFormat( | |
| "Expat %d.%d.%d", XML_MAJOR_VERSION, | |
| XML_MINOR_VERSION, XML_MICRO_VERSION); | |
| } | |
| static PyGetSetDef xmlparser_getsetlist[] = { | |
| {"version", xmlparser_version_getter, NULL, NULL}, | |
| {NULL}, | |
| }; | |
| #define clinic_state() (get_elementtree_state_by_type(Py_TYPE(self))) | |
| #include "clinic/_elementtree.c.h" | |
| #undef clinic_state | |
| static PyMethodDef element_methods[] = { | |
| _ELEMENTTREE_ELEMENT_CLEAR_METHODDEF | |
| _ELEMENTTREE_ELEMENT_GET_METHODDEF | |
| _ELEMENTTREE_ELEMENT_SET_METHODDEF | |
| _ELEMENTTREE_ELEMENT_FIND_METHODDEF | |
| _ELEMENTTREE_ELEMENT_FINDTEXT_METHODDEF | |
| _ELEMENTTREE_ELEMENT_FINDALL_METHODDEF | |
| _ELEMENTTREE_ELEMENT_APPEND_METHODDEF | |
| _ELEMENTTREE_ELEMENT_EXTEND_METHODDEF | |
| _ELEMENTTREE_ELEMENT_INSERT_METHODDEF | |
| _ELEMENTTREE_ELEMENT_REMOVE_METHODDEF | |
| _ELEMENTTREE_ELEMENT_ITER_METHODDEF | |
| _ELEMENTTREE_ELEMENT_ITERTEXT_METHODDEF | |
| _ELEMENTTREE_ELEMENT_ITERFIND_METHODDEF | |
| _ELEMENTTREE_ELEMENT_ITEMS_METHODDEF | |
| _ELEMENTTREE_ELEMENT_KEYS_METHODDEF | |
| _ELEMENTTREE_ELEMENT_MAKEELEMENT_METHODDEF | |
| _ELEMENTTREE_ELEMENT___COPY___METHODDEF | |
| _ELEMENTTREE_ELEMENT___DEEPCOPY___METHODDEF | |
| _ELEMENTTREE_ELEMENT___SIZEOF___METHODDEF | |
| _ELEMENTTREE_ELEMENT___GETSTATE___METHODDEF | |
| _ELEMENTTREE_ELEMENT___SETSTATE___METHODDEF | |
| {NULL, NULL} | |
| }; | |
| static struct PyMemberDef element_members[] = { | |
| {"__weaklistoffset__", Py_T_PYSSIZET, offsetof(ElementObject, weakreflist), Py_READONLY}, | |
| {NULL}, | |
| }; | |
| static PyGetSetDef element_getsetlist[] = { | |
| {"tag", | |
| element_tag_getter, | |
| element_tag_setter, | |
| "A string identifying what kind of data this element represents"}, | |
| {"text", | |
| element_text_getter, | |
| element_text_setter, | |
| "A string of text directly after the start tag, or None"}, | |
| {"tail", | |
| element_tail_getter, | |
| element_tail_setter, | |
| "A string of text directly after the end tag, or None"}, | |
| {"attrib", | |
| element_attrib_getter, | |
| element_attrib_setter, | |
| "A dictionary containing the element's attributes"}, | |
| {NULL}, | |
| }; | |
| PyDoc_STRVAR(element_doc, | |
| "Element(tag, attrib={}, **extra)\n" | |
| "--\n" | |
| "\n" | |
| "An XML element.\n" | |
| "\n" | |
| "This class is the reference implementation of the Element interface.\n" | |
| "\n" | |
| "An element's length is its number of subelements. That means if you\n" | |
| "want to check if an element is truly empty, you should check BOTH\n" | |
| "its length AND its text attribute.\n" | |
| "\n" | |
| "*tag* is the element name. *attrib* is an optional dictionary\n" | |
| "containing element attributes. *extra* are additional element\n" | |
| "attributes given as keyword arguments.\n" | |
| "\n" | |
| "Example form:\n" | |
| " <tag attrib>text<child/>...</tag>tail"); | |
| static PyType_Slot element_slots[] = { | |
| {Py_tp_doc, (void *)element_doc}, | |
| {Py_tp_dealloc, element_dealloc}, | |
| {Py_tp_repr, element_repr}, | |
| {Py_tp_getattro, PyObject_GenericGetAttr}, | |
| {Py_tp_traverse, element_gc_traverse}, | |
| {Py_tp_clear, element_gc_clear}, | |
| {Py_tp_methods, element_methods}, | |
| {Py_tp_members, element_members}, | |
| {Py_tp_getset, element_getsetlist}, | |
| {Py_tp_init, element_init}, | |
| {Py_tp_alloc, PyType_GenericAlloc}, | |
| {Py_tp_new, element_new}, | |
| {Py_sq_length, element_length}, | |
| {Py_sq_item, element_getitem}, | |
| {Py_sq_ass_item, element_setitem}, | |
| {Py_nb_bool, element_bool}, | |
| {Py_mp_length, element_length}, | |
| {Py_mp_subscript, element_subscr}, | |
| {Py_mp_ass_subscript, element_ass_subscr}, | |
| {0, NULL}, | |
| }; | |
| static PyType_Spec element_spec = { | |
| .name = "xml.etree.ElementTree.Element", | |
| .basicsize = sizeof(ElementObject), | |
| .flags = (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC | | |
| Py_TPFLAGS_IMMUTABLETYPE), | |
| .slots = element_slots, | |
| }; | |
| static PyMethodDef treebuilder_methods[] = { | |
| _ELEMENTTREE_TREEBUILDER_DATA_METHODDEF | |
| _ELEMENTTREE_TREEBUILDER_START_METHODDEF | |
| _ELEMENTTREE_TREEBUILDER_END_METHODDEF | |
| _ELEMENTTREE_TREEBUILDER_COMMENT_METHODDEF | |
| _ELEMENTTREE_TREEBUILDER_PI_METHODDEF | |
| _ELEMENTTREE_TREEBUILDER_CLOSE_METHODDEF | |
| {NULL, NULL} | |
| }; | |
| static PyType_Slot treebuilder_slots[] = { | |
| {Py_tp_doc, (void *)_elementtree_TreeBuilder___init____doc__}, | |
| {Py_tp_dealloc, treebuilder_dealloc}, | |
| {Py_tp_traverse, treebuilder_gc_traverse}, | |
| {Py_tp_clear, treebuilder_gc_clear}, | |
| {Py_tp_methods, treebuilder_methods}, | |
| {Py_tp_init, _elementtree_TreeBuilder___init__}, | |
| {Py_tp_alloc, PyType_GenericAlloc}, | |
| {Py_tp_new, treebuilder_new}, | |
| {0, NULL}, | |
| }; | |
| static PyType_Spec treebuilder_spec = { | |
| .name = "xml.etree.ElementTree.TreeBuilder", | |
| .basicsize = sizeof(TreeBuilderObject), | |
| .flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_IMMUTABLETYPE, | |
| .slots = treebuilder_slots, | |
| }; | |
| static PyMethodDef xmlparser_methods[] = { | |
| _ELEMENTTREE_XMLPARSER_FEED_METHODDEF | |
| _ELEMENTTREE_XMLPARSER_CLOSE_METHODDEF | |
| _ELEMENTTREE_XMLPARSER_FLUSH_METHODDEF | |
| _ELEMENTTREE_XMLPARSER__PARSE_WHOLE_METHODDEF | |
| _ELEMENTTREE_XMLPARSER__SETEVENTS_METHODDEF | |
| {NULL, NULL} | |
| }; | |
| static PyType_Slot xmlparser_slots[] = { | |
| {Py_tp_doc, (void *)_elementtree_XMLParser___init____doc__}, | |
| {Py_tp_dealloc, xmlparser_dealloc}, | |
| {Py_tp_traverse, xmlparser_gc_traverse}, | |
| {Py_tp_clear, xmlparser_gc_clear}, | |
| {Py_tp_methods, xmlparser_methods}, | |
| {Py_tp_members, xmlparser_members}, | |
| {Py_tp_getset, xmlparser_getsetlist}, | |
| {Py_tp_init, _elementtree_XMLParser___init__}, | |
| {Py_tp_alloc, PyType_GenericAlloc}, | |
| {Py_tp_new, xmlparser_new}, | |
| {0, NULL}, | |
| }; | |
| static PyType_Spec xmlparser_spec = { | |
| .name = "xml.etree.ElementTree.XMLParser", | |
| .basicsize = sizeof(XMLParserObject), | |
| .flags = (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC | | |
| Py_TPFLAGS_IMMUTABLETYPE), | |
| .slots = xmlparser_slots, | |
| }; | |
| /* ==================================================================== */ | |
| /* python module interface */ | |
| static PyMethodDef _functions[] = { | |
| _ELEMENTTREE_SUBELEMENT_METHODDEF | |
| _ELEMENTTREE__SET_FACTORIES_METHODDEF | |
| {NULL, NULL} | |
| }; | |
| #define CREATE_TYPE(module, type, spec) \ | |
| do { \ | |
| if (type != NULL) { \ | |
| break; \ | |
| } \ | |
| type = (PyTypeObject *)PyType_FromModuleAndSpec(module, spec, NULL); \ | |
| if (type == NULL) { \ | |
| goto error; \ | |
| } \ | |
| } while (0) | |
| static int | |
| module_exec(PyObject *m) | |
| { | |
| elementtreestate *st = get_elementtree_state(m); | |
| /* Initialize object types */ | |
| CREATE_TYPE(m, st->ElementIter_Type, &elementiter_spec); | |
| CREATE_TYPE(m, st->TreeBuilder_Type, &treebuilder_spec); | |
| CREATE_TYPE(m, st->Element_Type, &element_spec); | |
| CREATE_TYPE(m, st->XMLParser_Type, &xmlparser_spec); | |
| st->deepcopy_obj = PyImport_ImportModuleAttrString("copy", "deepcopy"); | |
| if (st->deepcopy_obj == NULL) { | |
| goto error; | |
| } | |
| assert(!PyErr_Occurred()); | |
| if (!(st->elementpath_obj = PyImport_ImportModule("xml.etree.ElementPath"))) | |
| goto error; | |
| /* link against pyexpat */ | |
| if (!(st->expat_capsule = PyImport_ImportModuleAttrString("pyexpat", "expat_CAPI"))) | |
| goto error; | |
| if (!(st->expat_capi = PyCapsule_GetPointer(st->expat_capsule, PyExpat_CAPSULE_NAME))) | |
| goto error; | |
| if (st->expat_capi) { | |
| /* check that it's usable */ | |
| if (strcmp(st->expat_capi->magic, PyExpat_CAPI_MAGIC) != 0 || | |
| (size_t)st->expat_capi->size < sizeof(struct PyExpat_CAPI) || | |
| st->expat_capi->MAJOR_VERSION != XML_MAJOR_VERSION || | |
| st->expat_capi->MINOR_VERSION != XML_MINOR_VERSION || | |
| st->expat_capi->MICRO_VERSION != XML_MICRO_VERSION) { | |
| PyErr_SetString(PyExc_ImportError, | |
| "pyexpat version is incompatible"); | |
| goto error; | |
| } | |
| } else { | |
| goto error; | |
| } | |
| st->str_append = PyUnicode_InternFromString("append"); | |
| if (st->str_append == NULL) { | |
| goto error; | |
| } | |
| st->str_find = PyUnicode_InternFromString("find"); | |
| if (st->str_find == NULL) { | |
| goto error; | |
| } | |
| st->str_findall = PyUnicode_InternFromString("findall"); | |
| if (st->str_findall == NULL) { | |
| goto error; | |
| } | |
| st->str_findtext = PyUnicode_InternFromString("findtext"); | |
| if (st->str_findtext == NULL) { | |
| goto error; | |
| } | |
| st->str_iterfind = PyUnicode_InternFromString("iterfind"); | |
| if (st->str_iterfind == NULL) { | |
| goto error; | |
| } | |
| st->str_tail = PyUnicode_InternFromString("tail"); | |
| if (st->str_tail == NULL) { | |
| goto error; | |
| } | |
| st->str_text = PyUnicode_InternFromString("text"); | |
| if (st->str_text == NULL) { | |
| goto error; | |
| } | |
| st->str_doctype = PyUnicode_InternFromString("doctype"); | |
| if (st->str_doctype == NULL) { | |
| goto error; | |
| } | |
| st->parseerror_obj = PyErr_NewException( | |
| "xml.etree.ElementTree.ParseError", PyExc_SyntaxError, NULL | |
| ); | |
| if (PyModule_AddObjectRef(m, "ParseError", st->parseerror_obj) < 0) { | |
| goto error; | |
| } | |
| PyTypeObject *types[] = { | |
| st->Element_Type, | |
| st->TreeBuilder_Type, | |
| st->XMLParser_Type | |
| }; | |
| for (size_t i = 0; i < Py_ARRAY_LENGTH(types); i++) { | |
| if (PyModule_AddType(m, types[i]) < 0) { | |
| goto error; | |
| } | |
| } | |
| return 0; | |
| error: | |
| return -1; | |
| } | |
| static struct PyModuleDef_Slot elementtree_slots[] = { | |
| _Py_ABI_SLOT, | |
| {Py_mod_exec, module_exec}, | |
| {Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED}, | |
| {Py_mod_gil, Py_MOD_GIL_NOT_USED}, | |
| {0, NULL}, | |
| }; | |
| static struct PyModuleDef elementtreemodule = { | |
| .m_base = PyModuleDef_HEAD_INIT, | |
| .m_name = "_elementtree", | |
| .m_size = sizeof(elementtreestate), | |
| .m_methods = _functions, | |
| .m_slots = elementtree_slots, | |
| .m_traverse = elementtree_traverse, | |
| .m_clear = elementtree_clear, | |
| .m_free = elementtree_free, | |
| }; | |
| PyMODINIT_FUNC | |
| PyInit__elementtree(void) | |
| { | |
| return PyModuleDef_Init(&elementtreemodule); | |
| } |