# -*- coding: utf-8 -*-
"""
Tests for different Element class lookup mechanisms.
"""
import unittest, os.path, sys, gc
this_dir = os.path.dirname(__file__)
if this_dir not in sys.path:
sys.path.insert(0, this_dir) # needed for Py3
from common_imports import etree, HelperTestCase, SillyFileLike, fileInTestDir
from common_imports import canonicalize, _bytes, _str, BytesIO, StringIO
xml_str = _bytes('''\
<root xmlns="myNS" xmlns:other="otherNS">
<c1 a1="A1" a2="A2" other:a3="A3">
<c2 a1="C2">0</c2>
<c2>1</c2>
<other:c2>2</other:c2>
</c1>
</root>''')
class ProxyTestCase(HelperTestCase):
"""Basic tests for element proxy behaviour.
"""
etree = etree
def test_proxy_reuse(self):
root = etree.XML('<a><b><c/></b></a>')
b = root.find('b')
self.assertTrue(b is root[0])
def test_proxy_reuse_after_gc(self):
root = etree.XML('<a><b><c/></b></a>')
b = root.find('b')
self.assertTrue(self.etree.iselement(b))
gc.collect()
self.assertTrue(b is root[0])
def test_proxy_reuse_after_del_root(self):
root = etree.XML('<a><b><c/></b></a>')
b = root.find('b')
self.assertTrue(self.etree.iselement(b))
c = b.find('c')
self.assertTrue(self.etree.iselement(c))
del root
gc.collect()
self.assertTrue(b[0] is c)
def test_proxy_hashing(self):
root = etree.XML('<a><b><c/></b></a>')
old_elements = set(root.iter())
elements = root.iter()
del root
gc.collect()
missing = len(old_elements)
self.assertEqual(3, missing)
for new in elements:
for old in old_elements:
if old == new:
self.assertTrue(old is new)
missing -= 1
break
else:
self.assertTrue(False, "element '%s' is missing" % new.tag)
self.assertEqual(0, missing)
def test_element_base(self):
el = self.etree.ElementBase()
self.assertEqual('ElementBase', el.tag)
root = self.etree.ElementBase()
root.append(el)
self.assertEqual('ElementBase', root[0].tag)
def test_element_base_children(self):
el = self.etree.ElementBase(etree.ElementBase())
self.assertEqual('ElementBase', el.tag)
self.assertEqual(1, len(el))
self.assertEqual('ElementBase', el[0].tag)
root = self.etree.ElementBase()
root.append(el)
self.assertEqual('ElementBase', root[0].tag)
self.assertEqual('ElementBase', root[0][0].tag)
def test_comment_base(self):
el = self.etree.CommentBase('some text')
self.assertEqual(self.etree.Comment, el.tag)
self.assertEqual('some text', el.text)
root = self.etree.Element('root')
root.append(el)
self.assertEqual('some text', root[0].text)
def test_pi_base(self):
el = self.etree.PIBase('the target', 'some text')
self.assertEqual(self.etree.ProcessingInstruction, el.tag)
self.assertEqual('some text', el.text)
root = self.etree.Element('root')
root.append(el)
self.assertEqual('some text', root[0].text)
class ClassLookupTestCase(HelperTestCase):
"""Test cases for different Element class lookup mechanisms.
"""
etree = etree
def tearDown(self):
etree.set_element_class_lookup()
super(ClassLookupTestCase, self).tearDown()
def test_namespace_lookup(self):
class TestElement(etree.ElementBase):
FIND_ME = "namespace class"
lookup = etree.ElementNamespaceClassLookup()
etree.set_element_class_lookup(lookup)
ns = lookup.get_namespace("myNS")
ns[None] = TestElement
root = etree.XML(xml_str)
self.assertEqual(root.FIND_ME,
TestElement.FIND_ME)
self.assertEqual(root[0].FIND_ME,
TestElement.FIND_ME)
self.assertFalse(hasattr(root[0][-1], 'FIND_ME'))
def test_default_class_lookup(self):
class TestElement(etree.ElementBase):
FIND_ME = "default element"
class TestComment(etree.CommentBase):
FIND_ME = "default comment"
class TestPI(etree.PIBase):
FIND_ME = "default pi"
parser = etree.XMLParser()
lookup = etree.ElementDefaultClassLookup(
element=TestElement, comment=TestComment, pi=TestPI)
parser.set_element_class_lookup(lookup)
root = etree.XML(_bytes("""<?xml version='1.0'?>
<root>
<?myPI?>
<!-- hi -->
</root>
"""), parser)
self.assertEqual("default element", root.FIND_ME)
self.assertEqual("default pi", root[0].FIND_ME)
self.assertEqual("default comment", root[1].FIND_ME)
def test_default_class_lookup_pull_parser(self):
class TestElement(etree.ElementBase):
FIND_ME = "default element"
class TestComment(etree.CommentBase):
FIND_ME = "default comment"
class TestPI(etree.PIBase):
FIND_ME = "default pi"
parser = etree.XMLPullParser(events=('start', 'end', 'comment', 'pi'))
lookup = etree.ElementDefaultClassLookup(
element=TestElement, comment=TestComment, pi=TestPI)
parser.set_element_class_lookup(lookup)
events_seen = []
def add_events(events):
for ev, el in events:
events_seen.append((ev, el.FIND_ME))
parser.feed("""<?xml version='1.0'?>
<root>
<?myPI?>
""")
add_events(parser.read_events())
parser.feed("<!-- hi -->")
add_events(parser.read_events())
parser.feed("</root>")
root = parser.close()
add_events(parser.read_events())
self.assertEqual([
('start', "default element"),
('pi', "default pi"),
('comment', "default comment"),
('end', "default element"),
], events_seen)
self.assertEqual("default element", root.FIND_ME)
self.assertEqual("default pi", root[0].FIND_ME)
self.assertEqual("default comment", root[1].FIND_ME)
def test_evil_class_lookup(self):
class MyLookup(etree.CustomElementClassLookup):
def lookup(self, t, d, ns, name):
if name == 'none':
return None
elif name == 'obj':
return object()
else:
return etree.ElementBase
parser = etree.XMLParser()
parser.set_element_class_lookup(MyLookup())
root = etree.XML(_bytes('<none/>'), parser)
self.assertEqual('none', root.tag)
self.assertRaises(
TypeError,
etree.XML, _bytes("<obj />"), parser)
root = etree.XML(_bytes('<root/>'), parser)
self.assertEqual('root', root.tag)
def test_class_lookup_type_mismatch(self):
class MyLookup(etree.CustomElementClassLookup):
def lookup(self, t, d, ns, name):
if t == 'element':
if name == 'root':
return etree.ElementBase
return etree.CommentBase
elif t == 'comment':
return etree.PIBase
elif t == 'PI':
return etree.EntityBase
elif t == 'entity':
return etree.ElementBase
else:
raise ValueError('got type %s' % t)
parser = etree.XMLParser(resolve_entities=False)
parser.set_element_class_lookup(MyLookup())
root = etree.XML(_bytes('<root></root>'), parser)
self.assertEqual('root', root.tag)
self.assertEqual(etree.ElementBase, type(root))
root = etree.XML(_bytes("<root><test/></root>"), parser)
self.assertRaises(TypeError, root.__getitem__, 0)
root = etree.XML(_bytes("<root><!-- test --></root>"), parser)
self.assertRaises(TypeError, root.__getitem__, 0)
root = etree.XML(_bytes("<root><?test?></root>"), parser)
self.assertRaises(TypeError, root.__getitem__, 0)
root = etree.XML(
_bytes('<!DOCTYPE root [<!ENTITY myent "ent">]>'
'<root>&myent;</root>'),
parser)
self.assertRaises(TypeError, root.__getitem__, 0)
root = etree.XML(_bytes('<root><root/></root>'), parser)
self.assertEqual('root', root[0].tag)
def test_attribute_based_lookup(self):
class TestElement(etree.ElementBase):
FIND_ME = "attribute_based"
class_dict = {"A1" : TestElement}
lookup = etree.AttributeBasedElementClassLookup(
"a1", class_dict)
etree.set_element_class_lookup(lookup)
root = etree.XML(xml_str)
self.assertFalse(hasattr(root, 'FIND_ME'))
self.assertEqual(root[0].FIND_ME,
TestElement.FIND_ME)
self.assertFalse(hasattr(root[0][0], 'FIND_ME'))
def test_custom_lookup(self):
class TestElement(etree.ElementBase):
FIND_ME = "custom"
class MyLookup(etree.CustomElementClassLookup):
def lookup(self, t, d, ns, name):
if name == 'c1':
return TestElement
etree.set_element_class_lookup( MyLookup() )
root = etree.XML(xml_str)
self.assertFalse(hasattr(root, 'FIND_ME'))
self.assertEqual(root[0].FIND_ME,
TestElement.FIND_ME)
self.assertFalse(hasattr(root[0][1], 'FIND_ME'))
def test_custom_lookup_ns_fallback(self):
class TestElement1(etree.ElementBase):
FIND_ME = "custom"
class TestElement2(etree.ElementBase):
FIND_ME = "nsclasses"
class MyLookup(etree.CustomElementClassLookup):
def lookup(self, t, d, ns, name):
if name == 'c1':
return TestElement1
lookup = etree.ElementNamespaceClassLookup( MyLookup() )
etree.set_element_class_lookup(lookup)
ns = lookup.get_namespace("otherNS")
ns[None] = TestElement2
root = etree.XML(xml_str)
self.assertFalse(hasattr(root, 'FIND_ME'))
self.assertEqual(root[0].FIND_ME,
TestElement1.FIND_ME)
self.assertFalse(hasattr(root[0][1], 'FIND_ME'))
self.assertEqual(root[0][-1].FIND_ME,
TestElement2.FIND_ME)
def test_parser_based_lookup(self):
class TestElement(etree.ElementBase):
FIND_ME = "parser_based"
lookup = etree.ParserBasedElementClassLookup()
etree.set_element_class_lookup(lookup)
class MyLookup(etree.CustomElementClassLookup):
def lookup(self, t, d, ns, name):
return TestElement
parser = etree.XMLParser()
parser.set_element_class_lookup( MyLookup() )
root = etree.parse(BytesIO(xml_str), parser).getroot()
self.assertEqual(root.FIND_ME,
TestElement.FIND_ME)
self.assertEqual(root[0].FIND_ME,
TestElement.FIND_ME)
root = etree.parse(BytesIO(xml_str)).getroot()
self.assertFalse(hasattr(root, 'FIND_ME'))
self.assertFalse(hasattr(root[0], 'FIND_ME'))
def test_class_lookup_reentry(self):
XML = self.etree.XML
class TestElement(etree.ElementBase):
FIND_ME = "here"
root = None
class MyLookup(etree.CustomElementClassLookup):
el = None
def lookup(self, t, d, ns, name):
if root is not None: # not in the parser
if self.el is None and name == "a":
self.el = []
self.el.append(root.find(name))
return TestElement
parser = self.etree.XMLParser()
parser.set_element_class_lookup(MyLookup())
root = XML(_bytes('<root><a>A</a><b xmlns="test">B</b></root>'),
parser)
a = root[0]
self.assertEqual(a.tag, "a")
self.assertEqual(root[0].tag, "a")
del a
self.assertEqual(root[0].tag, "a")
def test_lookup_without_fallback(self):
class Lookup(etree.CustomElementClassLookup):
def __init__(self):
# no super call here, so no fallback is set
pass
def lookup(self, node_type, document, namespace, name):
return Foo
class Foo(etree.ElementBase):
def custom(self):
return "test"
parser = self.etree.XMLParser()
parser.set_element_class_lookup( Lookup() )
root = etree.XML('<foo/>', parser)
self.assertEqual("test", root.custom())
def test_suite():
suite = unittest.TestSuite()
suite.addTests([unittest.makeSuite(ProxyTestCase)])
suite.addTests([unittest.makeSuite(ClassLookupTestCase)])
return suite
if __name__ == '__main__':
print('to test use test.py %s' % __file__)