[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