[Lxml-checkins] r54508 - in lxml/branch/lxml-2.0/src/lxml: . tests

scoder at codespeak.net scoder at codespeak.net
Tue May 6 23:25:42 CEST 2008


Author: scoder
Date: Tue May  6 23:25:42 2008
New Revision: 54508

Modified:
   lxml/branch/lxml-2.0/src/lxml/apihelpers.pxi
   lxml/branch/lxml-2.0/src/lxml/lxml.etree.pyx
   lxml/branch/lxml-2.0/src/lxml/proxy.pxi
   lxml/branch/lxml-2.0/src/lxml/tests/test_threading.py
   lxml/branch/lxml-2.0/src/lxml/tree.pxd
Log:
large trunk merge of threading fix

Modified: lxml/branch/lxml-2.0/src/lxml/apihelpers.pxi
==============================================================================
--- lxml/branch/lxml-2.0/src/lxml/apihelpers.pxi	(original)
+++ lxml/branch/lxml-2.0/src/lxml/apihelpers.pxi	Tue May  6 23:25:42 2008
@@ -643,7 +643,7 @@
     _moveTail(c_next, c_node)
     if not attemptDeallocation(c_node):
         # make namespaces absolute
-        moveNodeToDocument(doc, c_node)
+        moveNodeToDocument(doc, c_node.doc, c_node)
     return 0
 
 cdef void _moveTail(xmlNode* c_tail, xmlNode* c_target):
@@ -709,6 +709,7 @@
     """
     cdef xmlNode* c_orig_neighbour
     cdef xmlNode* c_next
+    cdef xmlDoc*  c_source_doc
     cdef _Element element
     cdef Py_ssize_t seqlength, i, c
     cdef _node_to_node_function next_element
@@ -792,12 +793,13 @@
         for element in elements:
             assert element is not None, "Node must not be None"
             # move element and tail over
+            c_source_doc = element._c_node.doc
             c_next = element._c_node.next
             tree.xmlAddPrevSibling(c_node, element._c_node)
             _moveTail(c_next, element._c_node)
 
             # integrate element into new document
-            moveNodeToDocument(parent._doc, element._c_node)
+            moveNodeToDocument(parent._doc, c_source_doc, element._c_node)
 
             # stop at the end of the slice
             if slicelength > 0:
@@ -827,7 +829,9 @@
     """
     cdef xmlNode* c_next
     cdef xmlNode* c_node
+    cdef xmlDoc* c_source_doc
     c_node = child._c_node
+    c_source_doc = c_node.doc
     # store possible text node
     c_next = c_node.next
     # move node itself
@@ -836,7 +840,7 @@
     _moveTail(c_next, c_node)
     # uh oh, elements may be pointing to different doc when
     # parent element has moved; change them too..
-    moveNodeToDocument(parent._doc, c_node)
+    moveNodeToDocument(parent._doc, c_source_doc, c_node)
 
 cdef int _prependChild(_Element parent, _Element child) except -1:
     """Prepend a new child to a parent element.
@@ -844,7 +848,9 @@
     cdef xmlNode* c_next
     cdef xmlNode* c_child
     cdef xmlNode* c_node
+    cdef xmlDoc* c_source_doc
     c_node = child._c_node
+    c_source_doc = c_node.doc
     # store possible text node
     c_next = c_node.next
     # move node itself
@@ -857,14 +863,16 @@
     _moveTail(c_next, c_node)
     # uh oh, elements may be pointing to different doc when
     # parent element has moved; change them too..
-    moveNodeToDocument(parent._doc, c_node)
+    moveNodeToDocument(parent._doc, c_source_doc, c_node)
 
 cdef int _appendSibling(_Element element, _Element sibling) except -1:
     """Append a new child to a parent element.
     """
     cdef xmlNode* c_next
     cdef xmlNode* c_node
+    cdef xmlDoc* c_source_doc
     c_node = sibling._c_node
+    c_source_doc = c_node.doc
     # store possible text node
     c_next = c_node.next
     # move node itself
@@ -872,14 +880,16 @@
     _moveTail(c_next, c_node)
     # uh oh, elements may be pointing to different doc when
     # parent element has moved; change them too..
-    moveNodeToDocument(element._doc, c_node)
+    moveNodeToDocument(element._doc, c_source_doc, c_node)
 
 cdef int _prependSibling(_Element element, _Element sibling) except -1:
     """Append a new child to a parent element.
     """
     cdef xmlNode* c_next
     cdef xmlNode* c_node
+    cdef xmlDoc* c_source_doc
     c_node = sibling._c_node
+    c_source_doc = c_node.doc
     # store possible text node
     c_next = c_node.next
     # move node itself
@@ -887,7 +897,7 @@
     _moveTail(c_next, c_node)
     # uh oh, elements may be pointing to different doc when
     # parent element has moved; change them too..
-    moveNodeToDocument(element._doc, c_node)
+    moveNodeToDocument(element._doc, c_source_doc, c_node)
 
 cdef int isutf8(char* s):
     cdef char c

Modified: lxml/branch/lxml-2.0/src/lxml/lxml.etree.pyx
==============================================================================
--- lxml/branch/lxml-2.0/src/lxml/lxml.etree.pyx	(original)
+++ lxml/branch/lxml-2.0/src/lxml/lxml.etree.pyx	Tue May  6 23:25:42 2008
@@ -557,6 +557,7 @@
         """
         cdef xmlNode* c_node
         cdef xmlNode* c_next
+        cdef xmlDoc* c_source_doc
         cdef _Element element
         cdef bint left_to_right
         cdef Py_ssize_t slicelength, step
@@ -578,13 +579,14 @@
             c_node = _findChild(self._c_node, x)
             if c_node is NULL:
                 raise IndexError("list index out of range")
+            c_source_doc = element._c_node.doc
             c_next = element._c_node.next
             _removeText(c_node.next)
             tree.xmlReplaceNode(c_node, element._c_node)
             _moveTail(c_next, element._c_node)
-            moveNodeToDocument(self._doc, element._c_node)
+            moveNodeToDocument(self._doc, c_source_doc, element._c_node)
             if not attemptDeallocation(c_node):
-                moveNodeToDocument(self._doc, c_node)
+                moveNodeToDocument(self._doc, c_node.doc, c_node)
 
     def __delitem__(self, x):
         """__delitem__(self, x)
@@ -731,14 +733,16 @@
         """
         cdef xmlNode* c_node
         cdef xmlNode* c_next
+        cdef xmlDoc* c_source_doc
         c_node = _findChild(self._c_node, index)
         if c_node is NULL:
             _appendChild(self, element)
             return
+        c_source_doc = c_node.doc
         c_next = element._c_node.next
         tree.xmlAddPrevSibling(c_node, element._c_node)
         _moveTail(c_next, element._c_node)
-        moveNodeToDocument(self._doc, element._c_node)
+        moveNodeToDocument(self._doc, c_source_doc, element._c_node)
 
     def remove(self, _Element element not None):
         """remove(self, element)
@@ -756,7 +760,7 @@
         tree.xmlUnlinkNode(c_node)
         _moveTail(c_next, c_node)
         # fix namespace declarations
-        moveNodeToDocument(self._doc, c_node)
+        moveNodeToDocument(self._doc, c_node.doc, c_node)
 
     def replace(self, _Element old_element not None,
                 _Element new_element not None):
@@ -768,18 +772,20 @@
         cdef xmlNode* c_old_next
         cdef xmlNode* c_new_node
         cdef xmlNode* c_new_next
+        cdef xmlDoc* c_source_doc
         c_old_node = old_element._c_node
         if c_old_node.parent is not self._c_node:
             raise ValueError("Element is not a child of this node.")
         c_old_next = c_old_node.next
         c_new_node = new_element._c_node
         c_new_next = c_new_node.next
+        c_source_doc = c_new_node.doc
         tree.xmlReplaceNode(c_old_node, c_new_node)
         _moveTail(c_new_next, c_new_node)
         _moveTail(c_old_next, c_old_node)
-        moveNodeToDocument(self._doc, c_new_node)
+        moveNodeToDocument(self._doc, c_source_doc, c_new_node)
         # fix namespace declarations
-        moveNodeToDocument(self._doc, c_old_node)
+        moveNodeToDocument(self._doc, c_old_node.doc, c_old_node)
         
     # PROPERTIES
     property tag:

Modified: lxml/branch/lxml-2.0/src/lxml/proxy.pxi
==============================================================================
--- lxml/branch/lxml-2.0/src/lxml/proxy.pxi	(original)
+++ lxml/branch/lxml-2.0/src/lxml/proxy.pxi	Tue May  6 23:25:42 2008
@@ -4,7 +4,7 @@
 # structure of the respective node to avoid multiple instantiation of
 # the Python class
 
-cdef _Element getProxy(xmlNode* c_node):
+cdef inline _Element getProxy(xmlNode* c_node):
     """Get a proxy for a given node.
     """
     #print "getProxy for:", <int>c_node
@@ -13,10 +13,10 @@
     else:
         return None
 
-cdef int hasProxy(xmlNode* c_node):
+cdef inline int hasProxy(xmlNode* c_node):
     return c_node._private is not NULL
     
-cdef int _registerProxy(_Element proxy) except -1:
+cdef inline int _registerProxy(_Element proxy) except -1:
     """Register a proxy and type for the node it's proxying for.
     """
     cdef xmlNode* c_node
@@ -31,7 +31,7 @@
     proxy._gc_doc = <python.PyObject*>proxy._doc
     python.Py_INCREF(proxy._doc)
 
-cdef int _unregisterProxy(_Element proxy) except -1:
+cdef inline int _unregisterProxy(_Element proxy) except -1:
     """Unregister a proxy for the node it's proxying for.
     """
     cdef xmlNode* c_node
@@ -40,12 +40,24 @@
     c_node._private = NULL
     return 0
 
-cdef void _releaseProxy(_Element proxy):
+cdef inline void _releaseProxy(_Element proxy):
     """An additional DECREF for the document.
     """
     python.Py_XDECREF(proxy._gc_doc)
     proxy._gc_doc = NULL
 
+cdef inline void _updateProxyDocument(xmlNode* c_node, _Document doc):
+    """Replace the document reference of a proxy.
+
+    This may deallocate the original document of the proxy!
+    """
+    cdef _Element element = <_Element>c_node._private
+    if element._doc is not doc:
+        python.Py_INCREF(doc)
+        python.Py_DECREF(element._doc)
+        element._doc = doc
+        element._gc_doc = <python.PyObject*>doc
+
 ################################################################################
 # temporarily make a node the root node of its document
 
@@ -196,7 +208,74 @@
             c_new_ns = c_new_ns.next
         c_parent = c_parent.parent
 
-cdef int moveNodeToDocument(_Document doc, xmlNode* c_element) except -1:
+ctypedef struct _nscache:
+    xmlNs** new
+    xmlNs** old
+    cstd.size_t size
+    cstd.size_t last
+
+cdef int _growNsCache(_nscache* c_ns_cache) except -1:
+    cdef xmlNs** c_ns_ptr
+    if c_ns_cache.size == 0:
+        c_ns_cache.size = 20
+    else:
+        c_ns_cache.size *= 2
+    c_ns_ptr = <xmlNs**> cstd.realloc(
+        c_ns_cache.new, c_ns_cache.size * sizeof(xmlNs*))
+    if c_ns_ptr is not NULL:
+        c_ns_cache.new = c_ns_ptr
+        c_ns_ptr = <xmlNs**> cstd.realloc(
+            c_ns_cache.old, c_ns_cache.size * sizeof(xmlNs*))
+    if c_ns_ptr is not NULL:
+        c_ns_cache.old = c_ns_ptr
+    else:
+        cstd.free(c_ns_cache.new)
+        cstd.free(c_ns_cache.old)
+        python.PyErr_NoMemory()
+        return -1
+    return 0
+
+cdef inline int _appendToNsCache(_nscache* c_ns_cache,
+                                 xmlNs* c_old_ns, xmlNs* c_new_ns) except -1:
+    if c_ns_cache.last >= c_ns_cache.size:
+        _growNsCache(c_ns_cache)
+    c_ns_cache.old[c_ns_cache.last] = c_old_ns
+    c_ns_cache.new[c_ns_cache.last] = c_new_ns
+    c_ns_cache.last += 1
+
+cdef int _stripRedundantNamespaceDeclarations(
+    xmlNode* c_element, _nscache* c_ns_cache, xmlNs** c_del_ns_list) except -1:
+    """Removes namespace declarations from an element that are already
+    defined in its parents.  Does not free the xmlNs's, just prepends
+    them to the c_del_ns_list.
+    """
+    cdef xmlNs* c_ns
+    cdef xmlNs* c_ns_next
+    cdef xmlNs** c_nsdef
+    # use a xmlNs** to handle assignments to "c_element.nsDef" correctly
+    c_nsdef = &c_element.nsDef
+    while c_nsdef[0] is not NULL:
+        c_ns = tree.xmlSearchNsByHref(
+            c_element.doc, c_element.parent, c_nsdef[0].href)
+        if c_ns is NULL:
+            # new namespace href => keep and cache the ns declaration
+            _appendToNsCache(c_ns_cache, c_nsdef[0], c_nsdef[0])
+            c_nsdef = &c_nsdef[0].next
+        else:
+            # known namespace href => strip the ns
+            if c_ns is tree.xmlSearchNs(c_element.doc, c_element.parent,
+                                        c_ns.prefix):
+                # prefix is not shadowed by parents => ns is reusable
+                _appendToNsCache(c_ns_cache, c_nsdef[0], c_ns)
+            # cut out c_nsdef.next and prepend it to garbage chain
+            c_ns_next = c_nsdef[0].next
+            c_nsdef[0].next = c_del_ns_list[0]
+            c_del_ns_list[0] = c_nsdef[0]
+            c_nsdef[0] = c_ns_next
+    return 0
+
+cdef int moveNodeToDocument(_Document doc, xmlDoc* c_source_doc,
+                            xmlNode* c_element) except -1:
     """Fix the xmlNs pointers of a node and its subtree that were moved.
 
     Mainly copied from libxml2's xmlReconciliateNs().  Expects libxml2 doc
@@ -213,7 +292,11 @@
        prefix).  If a namespace is unknown, declare a new one on the
        node.
 
-    3) Set the Document reference to the new Document (if different).
+    3) Reassign the names of tags and attribute from the dict of the
+       target document *iff* it is different from the dict used in the
+       source subtree.
+
+    4) Set the Document reference to the new Document (if different).
        This is done on backtracking to keep the original Document
        alive as long as possible, until all its elements are updated.
 
@@ -221,96 +304,66 @@
     step 1), but freed only after the complete subtree was traversed
     and all occurrences were replaced by tree-internal pointers.
     """
-    cdef _Element element
-    cdef xmlDoc* c_doc
     cdef xmlNode* c_start_node
     cdef xmlNode* c_node
-    cdef xmlNs** c_ns_ptr
-    cdef xmlNs** c_ns_new_cache
-    cdef xmlNs** c_ns_old_cache
+    cdef char* c_name
+    cdef _nscache c_ns_cache
     cdef xmlNs* c_ns
     cdef xmlNs* c_ns_next
     cdef xmlNs* c_nsdef
-    cdef xmlNs* c_new_ns
-    cdef xmlNs* c_del_ns
-    cdef cstd.size_t i, c_cache_size, c_cache_last
+    cdef xmlNs* c_del_ns_list
+    cdef cstd.size_t i
+    cdef tree.xmlDict* c_dict
 
     if not tree._isElementOrXInclude(c_element):
         return 0
 
-    c_doc = c_element.doc
+    # we need to copy the names of tags and attributes iff the element
+    # is based on a different libxml2 tag name dictionary
+    if doc._c_doc.dict is not c_source_doc.dict:
+        c_dict = doc._c_doc.dict
+    else:
+        c_dict = NULL
+
     c_start_node = c_element
-    c_ns_new_cache = NULL
-    c_ns_old_cache = NULL
-    c_cache_size = 0
-    c_cache_last = 0
-    c_del_ns = NULL
+    c_del_ns_list = NULL
+
+    c_ns_cache.new = NULL
+    c_ns_cache.old = NULL
+    c_ns_cache.size = 0
+    c_ns_cache.last = 0
 
     while c_element is not NULL:
         # 1) cut out namespaces defined here that are already known by
         #    the ancestors
-        c_nsdef = c_element.nsDef
-        if c_nsdef is not NULL:
-            # start with second nsdef to keep c_element.nsDef for now
-            while c_nsdef.next is not NULL:
-                if c_nsdef.next is c_element.ns:
-                    c_nsdef = c_nsdef.next
-                    continue
-                c_ns = tree.xmlSearchNsByHref(
-                    c_element.doc, c_element.parent, c_nsdef.next.href)
-                if c_ns is NULL:
-                    c_nsdef = c_nsdef.next
-                    continue
-                # cut out c_nsdef.next and prepend it to garbage chain
-                c_ns_next = c_nsdef.next.next
-                c_nsdef.next.next = c_del_ns
-                c_del_ns = c_nsdef.next
-                c_nsdef.next = c_ns_next
-            # now handle c_element.nsDef
-            c_ns = tree.xmlSearchNsByHref(
-                c_element.doc, c_element.parent, c_element.nsDef.href)
-            if c_ns is not NULL:
-                c_ns_next = c_element.nsDef.next
-                c_element.nsDef.next = c_del_ns
-                c_del_ns = c_element.nsDef
-                c_element.nsDef = c_ns_next
+        if c_element.nsDef is not NULL:
+            _stripRedundantNamespaceDeclarations(
+                c_element, &c_ns_cache, &c_del_ns_list)
 
-        # 2) make sure the namespace of an element and its attributes
-        #    is declared in this document (i.e. the node or its parents)
+        # 2) make sure the namespaces of an element and its attributes
+        #    are declared in this document (i.e. on the node or its parents)
         c_node = c_element
         while c_node is not NULL:
             if c_node.ns is not NULL:
-                for i from 0 <= i < c_cache_last:
-                    if c_node.ns is c_ns_old_cache[i]:
-                        c_node.ns = c_ns_new_cache[i]
+                for i from 0 <= i < c_ns_cache.last:
+                    if c_node.ns is c_ns_cache.old[i]:
+                        c_node.ns = c_ns_cache.new[i]
                         break
                 else:
                     # not in cache => find a replacement from this document
-                    c_new_ns = doc._findOrBuildNodeNs(
+                    c_ns = doc._findOrBuildNodeNs(
                         c_element, c_node.ns.href, c_node.ns.prefix)
-                    if c_cache_last >= c_cache_size:
-                        # must resize cache
-                        if c_cache_size == 0:
-                            c_cache_size = 20
-                        else:
-                            c_cache_size *= 2
-                        c_ns_ptr = <xmlNs**> cstd.realloc(
-                            c_ns_new_cache, c_cache_size * sizeof(xmlNs*))
-                        if c_ns_ptr is not NULL:
-                            c_ns_new_cache = c_ns_ptr
-                            c_ns_ptr = <xmlNs**> cstd.realloc(
-                                c_ns_old_cache, c_cache_size * sizeof(xmlNs*))
-                        if c_ns_ptr is not NULL:
-                            c_ns_old_cache = c_ns_ptr
-                        else:
-                            cstd.free(c_ns_new_cache)
-                            cstd.free(c_ns_old_cache)
-                            python.PyErr_NoMemory()
-                            return -1
-                    c_ns_new_cache[c_cache_last] = c_new_ns
-                    c_ns_old_cache[c_cache_last] = c_node.ns
-                    c_cache_last += 1
-                    c_node.ns = c_new_ns
+                    _appendToNsCache(&c_ns_cache, c_node.ns, c_ns)
+                    c_node.ns = c_ns
+
+            # 3) re-assign names from the target dict
+            if c_dict is not NULL:
+                c_name = tree.xmlDictLookup(c_dict, c_node.name, -1)
+                # c_name can be NULL on memory error, but we don't
+                # handle that here
+                if c_name is not NULL:
+                    c_node.name = c_name
+
             if c_node is c_element:
                 # after the element, continue with its attributes
                 c_node = <xmlNode*>c_element.properties
@@ -326,14 +379,9 @@
         if c_node is NULL:
             # no children => back off and continue with siblings and parents
 
-            # 3) fix _Document reference (may dealloc the original document!)
+            # 4) fix _Document reference (may dealloc the original document!)
             if c_element._private is not NULL:
-                element = <_Element>c_element._private
-                if element._doc is not doc:
-                    python.Py_INCREF(doc)
-                    python.Py_DECREF(element._doc)
-                    element._doc = doc
-                    element._gc_doc = <python.PyObject*>doc
+                _updateProxyDocument(c_element, doc)
 
             if c_element is c_start_node:
                 break # all done
@@ -349,14 +397,9 @@
                 if c_element is NULL or not tree._isElementOrXInclude(c_element):
                     break
 
-                # 3) fix _Document reference (may dealloc the original document!)
+                # 4) fix _Document reference (may dealloc the original document!)
                 if c_element._private is not NULL:
-                    element = <_Element>c_element._private
-                    if element._doc is not doc:
-                        python.Py_INCREF(doc)
-                        python.Py_DECREF(element._doc)
-                        element._doc = doc
-                        element._gc_doc = <python.PyObject*>doc
+                    _updateProxyDocument(c_element, doc)
 
                 if c_element is c_start_node:
                     break
@@ -370,13 +413,13 @@
         c_element = c_node
 
     # free now unused namespace declarations
-    if c_del_ns is not NULL:
-        tree.xmlFreeNsList(c_del_ns)
+    if c_del_ns_list is not NULL:
+        tree.xmlFreeNsList(c_del_ns_list)
 
     # cleanup
-    if c_ns_new_cache is not NULL:
-        cstd.free(c_ns_new_cache)
-    if c_ns_old_cache is not NULL:
-        cstd.free(c_ns_old_cache)
+    if c_ns_cache.new is not NULL:
+        cstd.free(c_ns_cache.new)
+    if c_ns_cache.old is not NULL:
+        cstd.free(c_ns_cache.old)
 
     return 0

Modified: lxml/branch/lxml-2.0/src/lxml/tests/test_threading.py
==============================================================================
--- lxml/branch/lxml-2.0/src/lxml/tests/test_threading.py	(original)
+++ lxml/branch/lxml-2.0/src/lxml/tests/test_threading.py	Tue May  6 23:25:42 2008
@@ -6,13 +6,18 @@
 
 import unittest, threading
 
-from common_imports import etree, HelperTestCase
+from common_imports import etree, HelperTestCase, StringIO
 
 class ThreadingTestCase(HelperTestCase):
     """Threading tests"""
     etree = etree
 
-    def test_subtree_copy(self):
+    def _run_thread(self, func):
+        thread = threading.Thread(target=func)
+        thread.start()
+        thread.join()
+
+    def test_subtree_copy_thread(self):
         tostring = self.etree.tostring
         XML = self.etree.XML
         xml = "<root><threadtag/></root>"
@@ -23,12 +28,113 @@
             main_root.append(thread_root[0])
             del thread_root
 
-        thread = threading.Thread(target=run_thread)
-        thread.start()
-        thread.join()
-
+        self._run_thread(run_thread)
         self.assertEquals(xml, tostring(main_root))
 
+    def test_main_xslt_in_thread(self):
+        XML = self.etree.XML
+        style = XML('''\
+<xsl:stylesheet version="1.0"
+    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+  <xsl:template match="*">
+    <foo><xsl:copy><xsl:value-of select="/a/b/text()" /></xsl:copy></foo>
+  </xsl:template>
+</xsl:stylesheet>''')
+        st = etree.XSLT(style)
+
+        result = []
+
+        def run_thread():
+            root = XML('<a><b>B</b><c>C</c></a>')
+            result.append( st(root) )
+
+        self._run_thread(run_thread)
+        self.assertEquals('''\
+<?xml version="1.0"?>
+<foo><a>B</a></foo>
+''',
+                          str(result[0]))
+
+    def test_thread_xslt(self):
+        XML = self.etree.XML
+        tostring = self.etree.tostring
+        root = XML('<a><b>B</b><c>C</c></a>')
+
+        def run_thread():
+            style = XML('''\
+    <xsl:stylesheet version="1.0"
+        xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+      <xsl:template match="*">
+        <foo><xsl:copy><xsl:value-of select="/a/b/text()" /></xsl:copy></foo>
+      </xsl:template>
+    </xsl:stylesheet>''')
+            st = etree.XSLT(style)
+            root.append( st(root).getroot() )
+
+        self._run_thread(run_thread)
+        self.assertEquals('<a><b>B</b><c>C</c><foo><a>B</a></foo></a>',
+                          tostring(root))
+
+    def test_thread_mix(self):
+        XML = self.etree.XML
+        Element = self.etree.Element
+        SubElement = self.etree.SubElement
+        tostring = self.etree.tostring
+        xml = '<a><b>B</b><c xmlns="test">C</c></a>'
+        root = XML(xml)
+        fragment = XML("<other><tags/></other>")
+
+        result = self.etree.Element("{myns}root", att = "someval")
+
+        def run_XML():
+            thread_root = XML(xml)
+            result.append(thread_root[0])
+            result.append(thread_root[-1])
+
+        def run_parse():
+            thread_root = self.etree.parse(StringIO(xml)).getroot()
+            result.append(thread_root[0])
+            result.append(thread_root[-1])
+
+        def run_move_main():
+            result.append(fragment[0])
+
+        def run_build():
+            result.append(
+                Element("{myns}foo", attrib={'{test}attr':'val'}))
+            SubElement(result, "{otherns}tasty")
+
+        def run_xslt():
+            style = XML('''\
+    <xsl:stylesheet version="1.0"
+        xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+      <xsl:template match="*">
+        <foo><xsl:copy><xsl:value-of select="/a/b/text()" /></xsl:copy></foo>
+      </xsl:template>
+    </xsl:stylesheet>''')
+            st = etree.XSLT(style)
+            result.append( st(root).getroot()[0] )
+
+        for test in (run_XML, run_parse, run_move_main, run_xslt):
+            tostring(result)
+            self._run_thread(test)
+
+        self.assertEquals(
+            '<ns0:root xmlns:ns0="myns" att="someval"><b>B</b><c xmlns="test">C</c><b>B</b><c xmlns="test">C</c><tags/><a>B</a></ns0:root>',
+            tostring(result))
+
+        def strip_first():
+            root = Element("newroot")
+            root.append(result[0])
+
+        while len(result):
+            self._run_thread(strip_first)
+
+        self.assertEquals(
+            '<ns0:root xmlns:ns0="myns" att="someval"/>',
+            tostring(result))
+
+
 def test_suite():
     suite = unittest.TestSuite()
     suite.addTests([unittest.makeSuite(ThreadingTestCase)])

Modified: lxml/branch/lxml-2.0/src/lxml/tree.pxd
==============================================================================
--- lxml/branch/lxml-2.0/src/lxml/tree.pxd	(original)
+++ lxml/branch/lxml-2.0/src/lxml/tree.pxd	Tue May  6 23:25:42 2008
@@ -52,12 +52,12 @@
     void xmlHashScan(xmlHashTable* table, xmlHashScanner f, void* data) nogil
     void* xmlHashLookup(xmlHashTable* table, char* name) nogil
 
-cdef extern from "libxml/tree.h":
-
-    # for some reason need to define this in this section;
+cdef extern from *: # actually "libxml/dict.h"
     # libxml/dict.h appears to be broken to include in C
     ctypedef struct xmlDict
-    
+    cdef char* xmlDictLookup(xmlDict* dict, char* name, int len) nogil
+
+cdef extern from "libxml/tree.h":
     ctypedef struct xmlDoc
     ctypedef struct xmlAttr
     ctypedef struct xmlNotationTable


More information about the lxml-checkins mailing list