[Lxml-checkins] r41009 - in lxml/trunk: . doc src/lxml src/lxml/tests

scoder at codespeak.net scoder at codespeak.net
Thu Mar 22 08:31:28 CET 2007


Author: scoder
Date: Thu Mar 22 08:31:26 2007
New Revision: 41009

Added:
   lxml/trunk/src/lxml/pyclasslookup.pyx
   lxml/trunk/src/lxml/tests/test_pyclasslookup.py
Modified:
   lxml/trunk/CHANGES.txt
   lxml/trunk/doc/element_classes.txt
   lxml/trunk/setupinfo.py
   lxml/trunk/src/lxml/apihelpers.pxi
   lxml/trunk/src/lxml/classlookup.pxi
   lxml/trunk/src/lxml/etree.pyx
   lxml/trunk/src/lxml/etreepublic.pxd
   lxml/trunk/src/lxml/public-api.pxi
   lxml/trunk/src/lxml/tests/test_elementtree.py
   lxml/trunk/src/lxml/tests/test_etree.py
Log:
lxml.pyclasslookup - element class lookup mechanism with tree access in Python space, collectAttributes() C-function, general cleanup

Modified: lxml/trunk/CHANGES.txt
==============================================================================
--- lxml/trunk/CHANGES.txt	(original)
+++ lxml/trunk/CHANGES.txt	Thu Mar 22 08:31:26 2007
@@ -2,6 +2,29 @@
 lxml changelog
 ==============
 
+under development
+=================
+
+Features added
+--------------
+
+* ``lxml.pyclasslookup`` module that can access the entire tree to determine a
+  suitable Element class
+
+* ``Element.values()`` to accompany the existing ``keys()`` and ``items()``
+
+* ``collectAttributes()`` C-function to build a list of attribute
+  keys/values/items for a libxml2 node
+
+Bugs fixed
+----------
+
+Other changes
+-------------
+
+* major rewrite of internal extension function setup
+
+
 1.3beta (2007-02-27)
 ====================
 

Modified: lxml/trunk/doc/element_classes.txt
==============================================================================
--- lxml/trunk/doc/element_classes.txt	(original)
+++ lxml/trunk/doc/element_classes.txt	Thu Mar 22 08:31:26 2007
@@ -89,7 +89,8 @@
   >>> parser.setElementClassLookup(parser_lookup)
 
 There is one drawback of the parser based scheme: the ``Element()`` factory
-creates a new document that deploys the default parser::
+does not know about your specialised parser and creates a new document that
+deploys the default parser::
 
   >>> el = etree.Element("root")
   >>> print isinstance(el, HonkElement)
@@ -231,8 +232,8 @@
 Custom element class lookup
 ...........................
 
-This is the most customisable way of finding element classes.  It allows you
-to implement a custom lookup scheme in a subclass::
+This is the most customisable way of finding element classes on a per-element
+basis.  It allows you to implement a custom lookup scheme in a subclass::
 
   >>> class MyLookup(etree.CustomElementClassLookup):
   ...     def lookup(self, node_type, document, namespace, name):
@@ -250,6 +251,45 @@
 per-parser setup.
 
 
+Tree based element class lookup in Python
+.........................................
+
+Taking more elaborate decisions than allowed by the custom scheme is difficult
+to achieve in pure Python.  It would require access to the tree - before the
+elements in the tree have been instantiated as Python Element objects.
+
+Luckily, there is a way to do this.  The separate module
+``lxml.pyclasslookup`` provides a lookup class called
+``PythonElementClassLookup`` that works similar to the custom lookup scheme::
+
+  >>> from lxml.pyclasslookup import PythonElementClassLookup
+  >>> class MyLookup(PythonElementClassLookup):
+  ...     def lookup(self, document, element):
+  ...         return MyElementClass # defined elsewhere
+
+  >>> parser = etree.XMLParser()
+  >>> parser.setElementClassLookup(MyLookup())
+
+As before, the first argument to the ``lookup()`` method is the opaque
+document instance that contains the Element.  The second arguments is a
+lightweight Element proxy implementation that is only valid during the lookup.
+Do not try to keep a reference to it.  Once the lookup is finished, the proxy
+will become invalid.  You will get an ``AssertionError`` if you access any of
+the properties or methods outside the scope of the lookup call where they were
+instantiated.
+
+During the lookup, the element object behaves mostly like a normal Element
+instance.  It provides the properties ``tag``, ``text``, ``tail`` etc. and
+supports indexing, slicing and the ``getchildren()``, ``getparent()``
+etc. methods.  It does *not* support iteration, nor does it support any kind
+of modification.  All of its properties are read-only and it cannot be removed
+or inserted into other trees.  You can use it as a starting point to freely
+traverse the tree and collect any kind of information that its elements
+provide.  Once you have taken the decision which class to use for this
+element, you can simply return it and have lxml take care of cleaning up the
+instantiated proxy classes.
+
+
 Implementing namespaces
 -----------------------
 

Modified: lxml/trunk/setupinfo.py
==============================================================================
--- lxml/trunk/setupinfo.py	(original)
+++ lxml/trunk/setupinfo.py	Thu Mar 22 08:31:26 2007
@@ -8,8 +8,9 @@
     PYREX_INSTALLED = False
 
 EXT_MODULES = [
-    ("etree",       "lxml.etree"),
-    ("objectify",   "lxml.objectify")
+    ("etree",         "lxml.etree"),
+    ("objectify",     "lxml.objectify"),
+    ("pyclasslookup", "lxml.pyclasslookup")
     ]
 
 

Modified: lxml/trunk/src/lxml/apihelpers.pxi
==============================================================================
--- lxml/trunk/src/lxml/apihelpers.pxi	(original)
+++ lxml/trunk/src/lxml/apihelpers.pxi	Thu Mar 22 08:31:26 2007
@@ -232,6 +232,29 @@
     tree.xmlRemoveProp(c_attr)
     return 0
 
+cdef object _collectAttributes(xmlNode* c_node, int collecttype):
+    """Collect all attributes of a node in a list.  Depending on collecttype,
+    it collects either the name (1), the value (2) or the name-value tuples.
+    """
+    cdef xmlAttr* c_attr
+    c_attr = c_node.properties
+    attributes = []
+    while c_attr is not NULL:
+        if c_attr.type == tree.XML_ATTRIBUTE_NODE:
+            if collecttype == 1:
+                item = _namespacedName(<xmlNode*>c_attr)
+            elif collecttype == 2:
+                item = _attributeValue(c_node, c_attr)
+            else:
+                item = (_namespacedName(<xmlNode*>c_attr),
+                        _attributeValue(c_node, c_attr))
+
+            ret = python.PyList_Append(attributes, item)
+            if ret:
+                raise
+        c_attr = c_attr.next
+    return attributes
+
 cdef object __RE_XML_ENCODING
 __RE_XML_ENCODING = re.compile(
     r'^(\s*<\?\s*xml[^>]+)\s+encoding\s*=\s*"[^"]*"\s*', re.U)

Modified: lxml/trunk/src/lxml/classlookup.pxi
==============================================================================
--- lxml/trunk/src/lxml/classlookup.pxi	(original)
+++ lxml/trunk/src/lxml/classlookup.pxi	Thu Mar 22 08:31:26 2007
@@ -206,7 +206,7 @@
 
     You can inherit from this class and override the method
 
-        lookup(type, doc, namespace, name)
+        lookup(self, type, doc, namespace, name)
 
     to lookup the element class for a node. Arguments of the method:
     * type:      one of 'element', 'comment', 'PI'

Modified: lxml/trunk/src/lxml/etree.pyx
==============================================================================
--- lxml/trunk/src/lxml/etree.pyx	(original)
+++ lxml/trunk/src/lxml/etree.pyx	Thu Mar 22 08:31:26 2007
@@ -717,8 +717,8 @@
         return "<Element %s at %x>" % (self.tag, id(self))
     
     def __getitem__(self, Py_ssize_t index):
-        """Returns the given subelement.
-        """        
+        """Returns the subelement at the given position.
+        """
         cdef xmlNode* c_node
         c_node = _findChild(self._c_node, index)
         if c_node is NULL:
@@ -739,10 +739,10 @@
             return []
         c = start
         result = []
-        doc = self._doc
         while c_node is not NULL and c < stop:
             if _isElement(c_node):
-                ret = python.PyList_Append(result, _elementFactory(doc, c_node))
+                ret = python.PyList_Append(
+                    result, _elementFactory(self._doc, c_node))
                 if ret:
                     raise
                 c = c + 1
@@ -858,29 +858,34 @@
         return _getAttributeValue(self, key, default)
 
     def keys(self):
-        """Gets a list of attribute names. The names are returned in an arbitrary
-        order (just like for an ordinary Python dictionary).
+        """Gets a list of attribute names.  The names are returned in an
+        arbitrary order (just like for an ordinary Python dictionary).
         """
-        return python.PySequence_List( _attributeIteratorFactory(self, 1) )
+        return _collectAttributes(self._c_node, 1)
+
+    def values(self):
+        """Gets element attribute values as a sequence of strings.  The
+        attributes are returned in an arbitrary order.
+        """
+        return _collectAttributes(self._c_node, 2)
 
     def items(self):
         """Gets element attributes, as a sequence. The attributes are returned in
         an arbitrary order.
         """
-        return python.PySequence_List( _attributeIteratorFactory(self, 3) )
+        return _collectAttributes(self._c_node, 3)
 
     def getchildren(self):
         """Returns all subelements. The elements are returned in document order.
         """
         cdef xmlNode* c_node
-        cdef _Document doc
         cdef int ret
         result = []
-        doc = self._doc
         c_node = self._c_node.children
         while c_node is not NULL:
             if _isElement(c_node):
-                ret = python.PyList_Append(result, _elementFactory(doc, c_node))
+                ret = python.PyList_Append(
+                    result, _elementFactory(self._doc, c_node))
                 if ret:
                     raise
             c_node = c_node.next
@@ -1441,28 +1446,25 @@
         return _getAttributeValue(self._element, key, default)
 
     def keys(self):
-        return python.PySequence_List(
-            _attributeIteratorFactory(self._element, 1) )
+        return _collectAttributes(self._element._c_node, 1)
 
     def __iter__(self):
-        return iter(self.keys())
+        return iter(_collectAttributes(self._element._c_node, 1))
     
     def iterkeys(self):
-        return iter(self.keys())
+        return iter(_collectAttributes(self._element._c_node, 1))
 
     def values(self):
-        return python.PySequence_List(
-            _attributeIteratorFactory(self._element, 2) )
+        return _collectAttributes(self._element._c_node, 2)
 
     def itervalues(self):
-        return iter(self.values())
+        return iter(_collectAttributes(self._element._c_node, 2))
 
     def items(self):
-        return python.PySequence_List(
-            _attributeIteratorFactory(self._element, 3) )
+        return _collectAttributes(self._element._c_node, 3)
 
     def iteritems(self):
-        return iter(self.items())
+        return iter(_collectAttributes(self._element._c_node, 3))
 
     def has_key(self, key):
         if key in self:

Modified: lxml/trunk/src/lxml/etreepublic.pxd
==============================================================================
--- lxml/trunk/src/lxml/etreepublic.pxd	(original)
+++ lxml/trunk/src/lxml/etreepublic.pxd	Thu Mar 22 08:31:26 2007
@@ -104,6 +104,9 @@
     # attributes must not be removed during iteration!
     cdef object iterattributes(_Element element, int keysvalues)
 
+    # return the list of all attribute names (1), values (2) or items (3)
+    cdef object collectAttributes(tree.xmlNode* c_element, int keysvalues)
+
     # set an attribute value on an element
     # on failure, sets an exception and returns -1
     cdef int setAttributeValue(_Element element, key, value) except -1

Modified: lxml/trunk/src/lxml/public-api.pxi
==============================================================================
--- lxml/trunk/src/lxml/public-api.pxi	(original)
+++ lxml/trunk/src/lxml/public-api.pxi	Thu Mar 22 08:31:26 2007
@@ -83,6 +83,9 @@
 cdef public object iterattributes(_Element element, int keysvalues):
     return _attributeIteratorFactory(element, keysvalues)
 
+cdef public object collectAttributes(xmlNode* c_element, int keysvalues):
+    return _collectAttributes(c_element, keysvalues)
+
 cdef public int setAttributeValue(_Element element, key, value) except -1:
     return _setAttributeValue(element, key, value)
 

Added: lxml/trunk/src/lxml/pyclasslookup.pyx
==============================================================================
--- (empty file)
+++ lxml/trunk/src/lxml/pyclasslookup.pyx	Thu Mar 22 08:31:26 2007
@@ -0,0 +1,277 @@
+from etreepublic cimport _Document, _Element, ElementBase
+from etreepublic cimport ElementClassLookup, FallbackElementClassLookup
+from etreepublic cimport elementFactory, import_etree
+from python cimport str, repr, isinstance, issubclass, iter
+from python cimport _cstr, Py_ssize_t
+cimport etreepublic as cetree
+cimport python
+cimport tree
+cimport cstd
+
+__all__ = ["PythonElementClassLookup"]
+
+cdef object etree
+from lxml import etree
+# initialize C-API of lxml.etree
+import_etree(etree)
+
+cdef class _ElementProxy:
+    cdef tree.xmlNode* _c_node
+    cdef object _source_proxy
+    cdef object _dependent_proxies
+
+    cdef int _assertNode(self) except -1:
+        """This is our way of saying: this proxy is invalid!
+        """
+        assert self._c_node is not NULL, "Proxy invalidated!"
+        return 0
+
+    property tag:
+        """Element tag
+        """
+        def __get__(self):
+            self._assertNode()
+            return cetree.namespacedName(self._c_node)
+
+    property text:
+        """Text before the first subelement. This is either a string or 
+        the value None, if there was no text.
+        """
+        def __get__(self):
+            self._assertNode()
+            return cetree.textOf(self._c_node)
+        
+    property tail:
+        """Text after this element's end tag, but before the next sibling
+        element's start tag. This is either a string or the value None, if
+        there was no text.
+        """
+        def __get__(self):
+            self._assertNode()
+            return cetree.tailOf(self._c_node)
+
+    property attrib:
+        def __get__(self):
+            self._assertNode()
+            return dict(cetree.collectAttributes(self._c_node, 3))
+
+    property prefix:
+        """Namespace prefix or None.
+        """
+        def __get__(self):
+            self._assertNode()
+            if self._c_node.ns is not NULL:
+                if self._c_node.ns.prefix is not NULL:
+                    return cetree.pyunicode(self._c_node.ns.prefix)
+            return None
+
+    property sourceline:
+        """Original line number as found by the parser or None if unknown.
+        """
+        def __get__(self):
+            cdef long line
+            self._assertNode()
+            line = tree.xmlGetLineNo(self._c_node)
+            if line > 0:
+                return line
+            else:
+                return None
+
+    def __repr__(self):
+        return "<Element %s at %x>" % (self.tag, id(self))
+    
+    def __getitem__(self, Py_ssize_t index):
+        """Returns the subelement at the given position.
+        """
+        cdef tree.xmlNode* c_node
+        c_node = cetree.findChild(self._c_node, index)
+        if c_node is NULL:
+            raise IndexError, "list index out of range"
+        return _newProxy(self._source_proxy, c_node)
+
+    def __getslice__(self, Py_ssize_t start, Py_ssize_t stop):
+        """Returns a list containing subelements in the given range.
+        """
+        cdef tree.xmlNode* c_node
+        cdef Py_ssize_t c
+        c_node = cetree.findChild(self._c_node, start)
+        if c_node is NULL:
+            return []
+        c = start
+        result = []
+        while c_node is not NULL and c < stop:
+            if tree._isElement(c_node):
+                ret = python.PyList_Append(
+                    result, _newProxy(self._source_proxy, c_node))
+                if ret:
+                    raise
+                c = c + 1
+            c_node = c_node.next
+        return result
+
+    def __len__(self):
+        """Returns the number of subelements.
+        """
+        cdef Py_ssize_t c
+        cdef tree.xmlNode* c_node
+        self._assertNode()
+        c = 0
+        c_node = self._c_node.children
+        while c_node is not NULL:
+            if tree._isElement(c_node):
+                c = c + 1
+            c_node = c_node.next
+        return c
+
+    def __nonzero__(self):
+        cdef tree.xmlNode* c_node
+        self._assertNode()
+        c_node = cetree.findChildBackwards(self._c_node, 0)
+        return c_node != NULL
+
+    def get(self, key, default=None):
+        """Gets an element attribute.
+        """
+        self._assertNode()
+        return _getAttributeValue(self._c_node, key, default)
+
+    def keys(self):
+        """Gets a list of attribute names. The names are returned in an
+        arbitrary order (just like for an ordinary Python dictionary).
+        """
+        self._assertNode()
+        return cetree.collectAttributes(self._c_node, 1)
+
+    def values(self):
+        """Gets element attributes, as a sequence. The attributes are returned
+        in an arbitrary order.
+        """
+        self._assertNode()
+        return cetree.collectAttributes(self._c_node, 2)
+
+    def items(self):
+        """Gets element attributes, as a sequence. The attributes are returned
+        in an arbitrary order.
+        """
+        self._assertNode()
+        return cetree.collectAttributes(self._c_node, 3)
+
+    def getchildren(self):
+        """Returns all subelements. The elements are returned in document
+        order.
+        """
+        cdef tree.xmlNode* c_node
+        cdef int ret
+        self._assertNode()
+        result = []
+        c_node = self._c_node.children
+        while c_node is not NULL:
+            if tree._isElement(c_node):
+                ret = python.PyList_Append(
+                    result, _newProxy(self._source_proxy, c_node))
+                if ret:
+                    raise
+            c_node = c_node.next
+        return result
+
+    def getparent(self):
+        """Returns the parent of this element or None for the root element.
+        """
+        cdef tree.xmlNode* c_parent
+        self._assertNode()
+        c_parent = self._c_node.parent
+        if c_parent is NULL or not tree._isElement(c_parent):
+            return None
+        else:
+            return _newProxy(self._source_proxy, c_parent)
+
+    def getnext(self):
+        """Returns the following sibling of this element or None.
+        """
+        cdef tree.xmlNode* c_node
+        self._assertNode()
+        c_node = cetree.nextElement(self._c_node)
+        if c_node is not NULL:
+            return _newProxy(self._source_proxy, c_node)
+        return None
+
+    def getprevious(self):
+        """Returns the preceding sibling of this element or None.
+        """
+        cdef tree.xmlNode* c_node
+        self._assertNode()
+        c_node = cetree.previousElement(self._c_node)
+        if c_node is not NULL:
+            return _newProxy(self._source_proxy, c_node)
+        return None
+
+cdef _ElementProxy _newProxy(_ElementProxy sourceProxy, tree.xmlNode* c_node):
+    cdef _ElementProxy el
+    el = _ElementProxy()
+    el._c_node = c_node
+    if sourceProxy is None:
+        sourceProxy = el
+        el._dependent_proxies = []
+    el._source_proxy = sourceProxy
+    python.PyList_Append(sourceProxy._dependent_proxies, el)
+    return el
+
+cdef _freeProxies(_ElementProxy sourceProxy):
+    cdef _ElementProxy el
+    if sourceProxy is None:
+        return
+    if sourceProxy._dependent_proxies is None:
+        return
+    for el in sourceProxy._dependent_proxies:
+        el._c_node = NULL
+    del sourceProxy._dependent_proxies[:]
+
+cdef object _getAttributeValue(tree.xmlNode* c_node, key, default):
+    cdef char* c_tag
+    cdef char* c_href
+    ns, tag = cetree.getNsTag(key)
+    c_tag = _cstr(tag)
+    if ns is None:
+        c_href = NULL
+    else:
+        c_href = _cstr(ns)
+    result = cetree.attributeValueFromNsName(c_node, c_href, c_tag)
+    if result is None:
+        return default
+    return result
+
+
+cdef class PythonElementClassLookup(FallbackElementClassLookup):
+    """Element class lookup based on a subclass method.
+
+    To use it, inherit from this class and override the method
+
+        lookup(self, document, node_proxy)
+
+    to lookup the element class for a node. The first argument is the opaque
+    document instance that contains the Element. The second arguments is a
+    lightweight Element proxy implementation that is only valid during the
+    lookup. Do not try to keep a reference to it. Once the lookup is done, the
+    proxy will be invalid.
+
+    If you return None from this method, the fallback will be called.
+    """
+    def __init__(self, ElementClassLookup fallback=None):
+        FallbackElementClassLookup.__init__(self, fallback)
+        self._lookup_function = _lookup_class
+
+    def lookup(self, doc, element):
+        return None
+
+cdef object _lookup_class(state, _Document doc, tree.xmlNode* c_node):
+    cdef PythonElementClassLookup lookup
+    cdef _ElementProxy proxy
+    lookup = <PythonElementClassLookup>state
+
+    proxy = _newProxy(None, c_node)
+    cls = lookup.lookup(doc, proxy)
+    _freeProxies(proxy)
+
+    if cls is not None:
+        return cls
+    return cetree.callLookupFallback(lookup, doc, c_node)

Modified: lxml/trunk/src/lxml/tests/test_elementtree.py
==============================================================================
--- lxml/trunk/src/lxml/tests/test_elementtree.py	(original)
+++ lxml/trunk/src/lxml/tests/test_elementtree.py	Thu Mar 22 08:31:26 2007
@@ -360,6 +360,16 @@
         keys.sort()
         self.assertEquals(['alpha', 'beta', 'gamma'], keys)
 
+    def test_attribute_items2(self):
+        XML = self.etree.XML
+        
+        root = XML('<doc alpha="Alpha" beta="Beta" gamma="Gamma"/>')
+        items = root.items()
+        items.sort()
+        self.assertEquals(
+            [('alpha','Alpha'), ('beta','Beta'), ('gamma','Gamma')],
+            items)
+
     def test_attribute_keys_ns(self):
         XML = self.etree.XML
 

Modified: lxml/trunk/src/lxml/tests/test_etree.py
==============================================================================
--- lxml/trunk/src/lxml/tests/test_etree.py	(original)
+++ lxml/trunk/src/lxml/tests/test_etree.py	Thu Mar 22 08:31:26 2007
@@ -371,6 +371,15 @@
         Element = self.etree.Element
         self.assertRaises(TypeError, Element('a').append, None)
 
+    # ET's Elements have items() and key(), but not values()
+    def test_attribute_values(self):
+        XML = self.etree.XML
+        
+        root = XML('<doc alpha="Alpha" beta="Beta" gamma="Gamma"/>')
+        values = root.values()
+        values.sort()
+        self.assertEquals(['Alpha', 'Beta', 'Gamma'], values)
+
     # gives error in ElementTree
     def test_comment_empty(self):
         Element = self.etree.Element

Added: lxml/trunk/src/lxml/tests/test_pyclasslookup.py
==============================================================================
--- (empty file)
+++ lxml/trunk/src/lxml/tests/test_pyclasslookup.py	Thu Mar 22 08:31:26 2007
@@ -0,0 +1,290 @@
+# -*- coding: utf-8 -*-
+
+"""
+Tests specific to the Python based class lookup.
+"""
+
+
+import unittest, operator
+
+from common_imports import etree, StringIO, HelperTestCase, fileInTestDir
+from common_imports import SillyFileLike, canonicalize, doctest
+from common_imports import itemgetter
+
+from lxml.pyclasslookup import PythonElementClassLookup
+
+xml_str = '''\
+<obj:root xmlns:obj="objectified" xmlns:other="otherNS">
+  <obj:c1 a1="A1" a2="A2" other:a3="A3">
+    <obj:c2>0</obj:c2>
+    <obj:c2>1</obj:c2>
+    <obj:c2>2</obj:c2>
+    <other:c2>3</other:c2>
+    <c2>3</c2>
+  </obj:c1>
+</obj:root>'''
+
+
+class PyClassLookupTestCase(HelperTestCase):
+    """Test cases for the lxml.pyclasslookup class lookup mechanism.
+    """
+    etree = etree
+    parser = etree.XMLParser()
+    Element = parser.makeelement
+
+    def tearDown(self):
+        self.parser.setElementClassLookup(None)
+
+    def _setClassLookup(self, lookup_function):
+        class Lookup(PythonElementClassLookup):
+            def lookup(self, *args):
+                return lookup_function(*args)
+        self.parser.setElementClassLookup( Lookup() )
+
+    def _buildElementClass(self):
+        class LocalElement(etree.ElementBase):
+            pass
+        return LocalElement
+
+    def XML(self, xml):
+        return self.etree.XML(xml, self.parser)
+
+    # --- Test cases
+
+    def test_lookup(self):
+        el_class = self._buildElementClass()
+        el_class.i = 1
+        def lookup(*args):
+            if el_class.i == 1:
+                el_class.i = 2
+            return el_class
+        self._setClassLookup(lookup)
+        root = self.XML(xml_str)
+        self.assertEquals(2, el_class.i)
+
+    def test_lookup_keep_ref_assertion(self):
+        el_class = self._buildElementClass()
+        el_class.EL = None
+        def lookup(doc, el):
+            if el_class.EL is None:
+                el_class.EL = el
+            return el_class
+        self._setClassLookup(lookup)
+        root = self.XML(xml_str)
+        self.assertNotEquals(None, el_class.EL)
+        self.assertRaises(AssertionError, el_class.EL.getchildren)
+
+    def test_lookup_tag(self):
+        el_class = self._buildElementClass()
+        el_class.TAG = None
+        def lookup(doc, el):
+            if el_class.TAG is None:
+                el_class.TAG = el.tag
+            return el_class
+        self._setClassLookup(lookup)
+        root = self.XML(xml_str)
+        self.assertNotEquals(None, root.TAG)
+        self.assertEquals(root.tag, root.TAG)
+
+    def test_lookup_text(self):
+        el_class = self._buildElementClass()
+        el_class.TEXT = None
+        def lookup(doc, el):
+            if el_class.TEXT is None:
+                el_class.TEXT = el.text
+            return el_class
+        self._setClassLookup(lookup)
+        root = self.XML(xml_str)
+        self.assertNotEquals(None, root.TEXT)
+        self.assertEquals(root.text, root.TEXT)
+
+    def test_lookup_tail(self):
+        el_class = self._buildElementClass()
+        el_class.TAIL = None
+        def lookup(doc, el):
+            if el_class.TAIL is None:
+                el_class.TAIL = el.tail
+            return el_class
+        self._setClassLookup(lookup)
+        root = self.XML(xml_str)
+        self.assertEquals(root.tail, root.TAIL)
+
+    def test_lookup_attrib(self):
+        el_class = self._buildElementClass()
+        el_class.ATTRIB = None
+        def lookup(doc, el):
+            if el_class.ATTRIB is None:
+                el_class.ATTRIB = el[0].attrib
+            return el_class
+        self._setClassLookup(lookup)
+        root = self.XML(xml_str)
+        items1 = root[0].attrib.items()
+        items1.sort()
+        items2 = root.ATTRIB.items()
+        items2.sort()
+        self.assertEquals(items1, items2)
+
+    def test_lookup_prefix(self):
+        el_class = self._buildElementClass()
+        el_class.PREFIX = None
+        def lookup(doc, el):
+            if el_class.PREFIX is None:
+                el_class.PREFIX = el.prefix
+            return el_class
+        self._setClassLookup(lookup)
+        root = self.XML(xml_str)
+        self.assertEquals(root.prefix, root.PREFIX)
+
+    def test_lookup_sourceline(self):
+        el_class = self._buildElementClass()
+        el_class.LINE = None
+        def lookup(doc, el):
+            if el_class.LINE is None:
+                el_class.LINE = el.sourceline
+            return el_class
+        self._setClassLookup(lookup)
+        root = self.XML(xml_str)
+        self.assertEquals(root.sourceline, root.LINE)
+
+    def test_lookup_getitem(self):
+        el_class = self._buildElementClass()
+        el_class.CHILD_TAG = None
+        def lookup(doc, el):
+            el_class.CHILD_TAG = el[0].tag
+            return el_class
+        self._setClassLookup(lookup)
+        root = self.XML(xml_str)
+        child_tag = root.CHILD_TAG
+        self.assertNotEquals(None, child_tag)
+        self.assertEquals(root[0].tag, child_tag)
+
+    def test_lookup_getitem_neg(self):
+        el_class = self._buildElementClass()
+        el_class.CHILD_TAG = None
+        def lookup(doc, el):
+            if el_class.CHILD_TAG is None:
+                el_class.CHILD_TAG = el[-1].tag
+            return el_class
+        self._setClassLookup(lookup)
+        root = self.XML(xml_str)
+        child_tag = root.CHILD_TAG
+        self.assertNotEquals(None, child_tag)
+        self.assertEquals(root[-1].tag, child_tag)
+
+    def test_lookup_getslice(self):
+        el_class = self._buildElementClass()
+        el_class.CHILD_TAGS = None
+        def lookup(doc, el):
+            if el_class.CHILD_TAGS is None:
+                el_class.CHILD_TAGS = [ c.tag for c in el[1:-1] ]
+            return el_class
+        self._setClassLookup(lookup)
+        root = self.XML(xml_str)
+        child_tags = root.CHILD_TAGS
+        self.assertNotEquals(None, child_tags)
+        self.assertEquals([ c.tag for c in root[1:-1] ],
+                          child_tags)
+
+    def test_lookup_len(self):
+        el_class = self._buildElementClass()
+        el_class.LEN = None
+        def lookup(doc, el):
+            if el_class.LEN is None:
+                el_class.LEN = len(el)
+            return el_class
+        self._setClassLookup(lookup)
+        root = self.XML(xml_str)
+        self.assertEquals(1, el_class.LEN)
+
+    def test_lookup_bool(self):
+        el_class = self._buildElementClass()
+        el_class.TRUE = None
+        def lookup(doc, el):
+            if el_class.TRUE is None:
+                el_class.TRUE = bool(el)
+            return el_class
+        self._setClassLookup(lookup)
+        root = self.XML(xml_str)
+        self.assert_(el_class.TRUE)
+
+    def test_lookup_get(self):
+        el_class = self._buildElementClass()
+        el_class.VAL = None
+        def lookup(doc, el):
+            if el_class.VAL is None:
+                el_class.VAL = el[0].get('a1')
+            return el_class
+        self._setClassLookup(lookup)
+        root = self.XML(xml_str)
+        self.assertNotEquals(None, el_class.VAL)
+        self.assertEquals(root[0].get('a1'), el_class.VAL)
+
+    def test_lookup_get_default(self):
+        el_class = self._buildElementClass()
+        default = str(id(el_class))
+        el_class.VAL = None
+        def lookup(doc, el):
+            if el_class.VAL is None:
+                el_class.VAL = el[0].get('unknownattribute', default)
+            return el_class
+        self._setClassLookup(lookup)
+        root = self.XML(xml_str)
+        self.assertEquals(default, el_class.VAL)
+
+    def test_lookup_getchildren(self):
+        el_class = self._buildElementClass()
+        el_class.CHILD_TAGS = None
+        def lookup(doc, el):
+            if el_class.CHILD_TAGS is None:
+                el_class.CHILD_TAGS = [ c.tag for c in el.getchildren() ]
+            return el_class
+        self._setClassLookup(lookup)
+        root = self.XML(xml_str)
+        child_tags = root.CHILD_TAGS
+        self.assertNotEquals(None, child_tags)
+        self.assertEquals([ c.tag for c in root.getchildren() ],
+                          child_tags)
+
+    def test_lookup_getparent(self):
+        el_class = self._buildElementClass()
+        el_class.PARENT = None
+        def lookup(doc, el):
+            if el_class.PARENT is None:
+                el_class.PARENT = el[0].getparent().tag
+            return el_class
+        self._setClassLookup(lookup)
+        root = self.XML(xml_str)
+        self.assertEquals(root.tag, root.PARENT)
+
+    def test_lookup_getnext(self):
+        el_class = self._buildElementClass()
+        el_class.NEXT = None
+        def lookup(doc, el):
+            if el_class.NEXT is None:
+                el_class.NEXT = el[0][1].getnext().tag
+            return el_class
+        self._setClassLookup(lookup)
+        root = self.XML(xml_str)
+        self.assertNotEquals(None, el_class.NEXT)
+        self.assertEquals(root[0][1].getnext().tag, el_class.NEXT)
+
+    def test_lookup_getprevious(self):
+        el_class = self._buildElementClass()
+        el_class.PREV = None
+        def lookup(doc, el):
+            if el_class.PREV is None:
+                el_class.PREV = el[0][1].getprevious().tag
+            return el_class
+        self._setClassLookup(lookup)
+        root = self.XML(xml_str)
+        self.assertNotEquals(None, el_class.PREV)
+        self.assertEquals(root[0][1].getprevious().tag, el_class.PREV)
+
+
+def test_suite():
+    suite = unittest.TestSuite()
+    suite.addTests([unittest.makeSuite(PyClassLookupTestCase)])
+    return suite
+
+if __name__ == '__main__':
+    unittest.main()


More information about the lxml-checkins mailing list