diff -r 026b9f5dd97f Lib/test/test_xml_etree.py --- a/Lib/test/test_xml_etree.py Fri Dec 07 10:18:22 2012 -0800 +++ b/Lib/test/test_xml_etree.py Fri Dec 07 21:51:50 2012 +0000 @@ -16,6 +16,8 @@ import html import io +import itertools +import pickle import sys import unittest import weakref @@ -1768,6 +1770,23 @@ class BasicElementTest(unittest.TestCase self.assertEqual(flag, True) self.assertEqual(wref(), None) + def assertEqualElements(self, alice, bob): + self.assertIsInstance(alice, ET.Element) + self.assertIsInstance(bob, ET.Element) + for x, y in itertools.zip_longest(alice.getchildren(), + bob.getchildren()): + # No need to test x,y for None --- the recursive call will do that. + self.assertEqualElements(x, y) + else: + properties = lambda elt: (elt.tag, elt.tail, elt.text, elt.attrib) + self.assertEqual(properties(alice), properties(bob)) + + def test_issue16076(self): + e = ET.Element('foo', attr='value') + e.text = "text goes here" + e.tail = "opposite of head" + f = pickle.loads(pickle.dumps(e)) + self.assertEqualElements(e, f) class ElementTreeTest(unittest.TestCase): def test_istype(self): @@ -2002,6 +2021,15 @@ class TreeBuilderTest(unittest.TestCase) ('html', '-//W3C//DTD XHTML 1.0 Transitional//EN', 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd')) + def test_issue16076(self): + tb = ET.TreeBuilder() + tb.start('tag', {}) + tb = pickle.loads(pickle.dumps(tb)) + tb.end('tag') + # At the moment, tb.close() returns None. + tree = tb.close() + self.assertIsInstance(tree, ET.Element) + class XincludeTest(unittest.TestCase): def _my_loader(self, href, parse): diff -r 026b9f5dd97f Modules/_elementtree.c --- a/Modules/_elementtree.c Fri Dec 07 10:18:22 2012 -0800 +++ b/Modules/_elementtree.c Fri Dec 07 21:51:50 2012 +0000 @@ -814,6 +814,86 @@ element_sizeof(PyObject* _self, PyObject return PyLong_FromSsize_t(result); } +static PyObject * +element_reduce(ElementObject *self) +{ + PyObject *children; + fprintf(stderr, "at element_reduce(%p)\n", self); + + if (self->extra && self->extra->length) { + /* Build a tuple of children. */ + int i; + children = PyTuple_New(self->extra->length); + for (i = 0; i < self->extra->length; i++) { + PyObject *child = self->extra->children[i]; + Py_INCREF(child); + PyTuple_SET_ITEM(children, i, child); + } + } + else { + /* No children. */ + Py_INCREF(Py_None); + children = Py_None; + } + + if (self->extra && self->extra->attrib && self->extra->attrib != Py_None) { + return Py_BuildValue("O(OO)(OOO)", Py_TYPE(self), self->tag, + self->extra->attrib, + self->text, self->tail, children); + } + else { + return Py_BuildValue("O(O)(OOO)", Py_TYPE(self), self->tag, + self->text, self->tail, children); + } +} +PyDoc_STRVAR(reduce_doc, "Return state information for pickling."); + +static PyObject * +element_setstate(ElementObject *self, PyObject *state) +{ + PyObject *text, *tail, *children; + fprintf(stderr, "at element_setstate(%p)\n", self); + if (!PyArg_ParseTuple(state, "OOO", &text, &tail, &children)) + return NULL; + + Py_CLEAR(self->text); + self->text = text; + Py_INCREF(self->text); + + Py_CLEAR(self->tail); + self->tail = tail; + Py_INCREF(self->tail); + + if (children == Py_None) { + /* No children. */ + Py_RETURN_NONE; + } + else if (!PyTuple_Check(children)) { + /* Can't happen. */ + PyErr_SetString(PyExc_SystemError, + "Expected _elementtree.Element.__setstate__() " + "to be passed a tuple"); + return NULL; + } + else { + /* Have children. */ + Py_ssize_t n = PyTuple_Size(children); + int i; + + /* attrib, if any, will have been set by now. */ + assert(! self->extra || ! self->extra->length); + if (element_resize(self, n) < 0) + return NULL; + + for (i = 0; i < n; i++) { + if (element_add_subelement(self, PyTuple_GET_ITEM(children, i)) < 0) + return NULL; + } + } + + Py_RETURN_NONE; +} + LOCAL(int) checkpath(PyObject* tag) { @@ -1582,6 +1662,8 @@ static PyMethodDef element_methods[] = { {"__copy__", (PyCFunction) element_copy, METH_VARARGS}, {"__deepcopy__", (PyCFunction) element_deepcopy, METH_VARARGS}, {"__sizeof__", element_sizeof, METH_NOARGS}, + {"__reduce__", (PyCFunction)element_reduce, METH_NOARGS, reduce_doc}, + {"__setstate__", (PyCFunction)element_setstate, METH_O}, {NULL, NULL} }; @@ -1686,7 +1768,7 @@ static PyMappingMethods element_as_mappi static PyTypeObject Element_Type = { PyVarObject_HEAD_INIT(NULL, 0) - "Element", sizeof(ElementObject), 0, + "_elementtree.Element", sizeof(ElementObject), 0, /* methods */ (destructor)element_dealloc, /* tp_dealloc */ 0, /* tp_print */ @@ -2002,7 +2084,7 @@ typedef struct { PyObject *data; /* data collector (string or list), or NULL */ - PyObject *stack; /* element stack */ + PyObject *stack; /* element stack; members after [index] place are NULL */ Py_ssize_t index; /* current stack size (0 means empty) */ PyObject *element_factory; @@ -2443,17 +2525,77 @@ treebuilder_start(TreeBuilderObject* sel return treebuilder_handle_start(self, tag, attrib); } +static PyObject * +treebuilder_reduce(TreeBuilderObject *self) +{ + PyObject *args, *stack, *result; + fprintf(stderr, "at treebuilder_reduce(%p)\n", self); + + if (self->events) { + /* XMLParser isn't pickleable, so don't try and support pickling when + we are a private instance used by it. */ + PyErr_SetString(PyExc_ValueError, + "Can't pickle TreeBuilder used from XMLParser"); + return NULL; + } + + /* Build a 0- or 1-elements tuple. */ + args = PyTuple_Pack(!!self->element_factory, self->element_factory); + stack = PyList_GetSlice(self->stack, 0, self->index); + result = Py_BuildValue("OO(OOOOO)", Py_TYPE(self), args, + self->root ? self->root : Py_None, + self->this ? self->this : Py_None, + self->last ? self->last : Py_None, + self->data ? self->data : Py_None, + stack); + Py_DECREF(args); + return result; +} + +static PyObject * +treebuilder_setstate(TreeBuilderObject *self, PyObject *state) +{ + PyObject *root, *this, *last, *data, *stack; + fprintf(stderr, "at treebuilder_setstate(%p)\n", self); + if (!PyArg_ParseTuple(state, "OOOOO", &root, &this, &last, &data, &stack)) + return NULL; + +#define SET_MEMBER(name) \ + do { \ + Py_CLEAR(self->name); \ + if (name == Py_None) \ + self->name = NULL; \ + else { \ + self->name = name; \ + Py_INCREF(self->name); \ + } \ + } while (0) + + SET_MEMBER(root); + SET_MEMBER(this); + SET_MEMBER(last); + SET_MEMBER(data); + SET_MEMBER(stack); + self->index = PyList_Size(self->stack); + +#undef SET_MEMBER + + Py_RETURN_NONE; +} + static PyMethodDef treebuilder_methods[] = { {"data", (PyCFunction) treebuilder_data, METH_VARARGS}, {"start", (PyCFunction) treebuilder_start, METH_VARARGS}, {"end", (PyCFunction) treebuilder_end, METH_VARARGS}, {"close", (PyCFunction) treebuilder_close, METH_VARARGS}, + {"__reduce__", (PyCFunction)treebuilder_reduce, METH_NOARGS, reduce_doc}, + {"__setstate__", (PyCFunction)treebuilder_setstate, METH_O}, {NULL, NULL} }; static PyTypeObject TreeBuilder_Type = { PyVarObject_HEAD_INIT(NULL, 0) - "TreeBuilder", sizeof(TreeBuilderObject), 0, + "_elementtree.TreeBuilder", sizeof(TreeBuilderObject), 0, /* methods */ (destructor)treebuilder_dealloc, /* tp_dealloc */ 0, /* tp_print */ @@ -3415,7 +3557,7 @@ xmlparser_getattro(XMLParserObject* self static PyTypeObject XMLParser_Type = { PyVarObject_HEAD_INIT(NULL, 0) - "XMLParser", sizeof(XMLParserObject), 0, + "_elementtree.XMLParser", sizeof(XMLParserObject), 0, /* methods */ (destructor)xmlparser_dealloc, /* tp_dealloc */ 0, /* tp_print */