1"""
2
3  dexml.test:  testcases for dexml module.
4
5"""
6
7import sys
8import os
9import os.path
10import unittest
11import doctest
12from xml.dom import minidom
13from io import StringIO
14
15import dexml2
16from dexml2 import fields
17
18
19def b(raw):
20    """Compatability wrapper for b"string" syntax."""
21    return raw.encode("ascii")
22
23
24def model_fields_equal(m1,m2):
25    """Check for equality by comparing model fields."""
26    for nm in m1.__class__._fields:
27        v1 = getattr(m1,nm.field_name)
28        v2 = getattr(m2,nm.field_name)
29        if isinstance(v1, dexml2.Model):
30            if not model_fields_equal(v1,v2):
31                return False
32        elif v1 != v2:
33            return False
34    return True
35
36
37
38class TestDexmlDocstring(unittest.TestCase):
39
40    def test_docstring(self):
41        """Test dexml docstrings
42
43        We don't do this on python3 because of the many small ways in
44        which the output has changed in that version.
45        """
46        if sys.version_info < (3,):
47            assert doctest.testmod(dexml2)[0] == 0
48
49    def test_readme_matches_docstring(self):
50        """Ensure that the README is in sync with the docstring.
51
52        This test should always pass; if the README is out of sync it just
53        updates it with the contents of dexml.__doc__.
54        """
55        dirname = os.path.dirname
56        readme = os.path.join(dirname(dirname(__file__)),"README.rst")
57        if not os.path.isfile(readme):
58            f = open(readme,"wb")
59            f.write(dexml2.__doc__.encode())
60            f.close()
61        else:
62            f = open(readme,"rb")
63            if f.read() != dexml2.__doc__:
64                f.close()
65                f = open(readme,"wb")
66                f.write(dexml2.__doc__.encode())
67                f.close()
68
69
70
71class TestDexml(unittest.TestCase):
72
73
74    def test_base(self):
75        """Test operation of a dexml.Model class with no fields."""
76        class hello(dexml2.Model):
77            pass
78
79        h = hello.parse("<hello />")
80        self.assertTrue(h)
81
82        h = hello.parse("<hello>\n</hello>")
83        self.assertTrue(h)
84
85        h = hello.parse("<hello>world</hello>")
86        self.assertTrue(h)
87
88        d = minidom.parseString("<hello>world</hello>")
89        h = hello.parse(d)
90        self.assertTrue(h)
91
92        self.assertRaises(dexml2.ParseError, hello.parse, "<Hello />")
93        self.assertRaises(dexml2.ParseError, hello.parse, "<hllo />")
94        self.assertRaises(dexml2.ParseError, hello.parse, "<hello xmlns='T:' />")
95
96        hello.meta.ignore_unknown_elements = False
97        self.assertRaises(dexml2.ParseError, hello.parse, "<hello>world</hello>")
98        hello.meta.ignore_unknown_elements = True
99
100        h = hello()
101        self.assertEqual(h.render(),'<?xml version="1.0" ?><hello />')
102        self.assertEqual(h.render(fragment=True),"<hello />")
103        self.assertEqual(h.render(pretty=True), '<?xml version="1.0" ?>\n<hello/>\n')
104        self.assertEqual(h.render(fragment=True, pretty=True), "<hello/>\n")
105        self.assertEqual(h.render(encoding="utf8"),b('<?xml version="1.0" encoding="utf8" ?><hello />'))
106        self.assertEqual(h.render(encoding="utf8", pretty=True), b('<?xml version="1.0" encoding="utf8" ?>\n<hello/>\n'))
107        self.assertEqual(h.render(encoding="utf8",fragment=True),b("<hello />"))
108        self.assertEqual(h.render(encoding="utf8", fragment=True, pretty=True), b("<hello/>\n"))
109
110        self.assertEqual(h.render(),"".join(h.irender()))
111        self.assertEqual(h.render(fragment=True),"".join(h.irender(fragment=True)))
112        self.assertEqual(h.render(encoding="utf8"),b("").join(h.irender(encoding="utf8")))
113        self.assertEqual(h.render(encoding="utf8",fragment=True),b("").join(h.irender(encoding="utf8",fragment=True)))
114
115
116    def test_errors_on_malformed_xml(self):
117        class hello(dexml2.Model):
118            pass
119
120        self.assertRaises(dexml2.XmlError, hello.parse, b("<hello>"))
121        self.assertRaises(dexml2.XmlError, hello.parse, b("<hello></helo>"))
122        self.assertRaises(dexml2.XmlError, hello.parse, b(""))
123
124        self.assertRaises(dexml2.XmlError, hello.parse, u"")
125        self.assertRaises(dexml2.XmlError, hello.parse, u"<hello>")
126        self.assertRaises(dexml2.XmlError, hello.parse, u"<hello></helo>")
127
128        self.assertRaises(dexml2.XmlError, hello.parse, StringIO(u"<hello>"))
129        self.assertRaises(dexml2.XmlError, hello.parse, StringIO(u"<hello></helo>"))
130        self.assertRaises(dexml2.XmlError, hello.parse, StringIO(u""))
131
132        self.assertRaises(ValueError,hello.parse,None)
133        self.assertRaises(ValueError,hello.parse,42)
134        self.assertRaises(ValueError,hello.parse,staticmethod)
135
136
137    def test_unicode_model_tagname(self):
138        """Test a dexml.Model class with a unicode tag name."""
139        class hello(dexml2.Model):
140            class meta:
141                tagname = u"hel\N{GREEK SMALL LETTER LAMDA}o"
142
143        h = hello.parse(u"<hel\N{GREEK SMALL LETTER LAMDA}o />")
144        self.assertTrue(h)
145
146        h = hello.parse(u"<hel\N{GREEK SMALL LETTER LAMDA}o>\n</hel\N{GREEK SMALL LETTER LAMDA}o>")
147        self.assertTrue(h)
148        self.assertRaises(dexml2.ParseError, hello.parse, u"<hello />")
149        self.assertRaises(dexml2.ParseError, hello.parse, u"<Hello />")
150        self.assertRaises(dexml2.ParseError, hello.parse, u"<hllo />")
151        self.assertRaises(dexml2.ParseError, hello.parse, u"<Hel\N{GREEK SMALL LETTER LAMDA}o />")
152
153        h = hello.parse(u"<hel\N{GREEK SMALL LETTER LAMDA}o>world</hel\N{GREEK SMALL LETTER LAMDA}o>")
154        self.assertTrue(h)
155
156        h = hello.parse(u"<?xml version='1.0' encoding='utf-8' ?><hel\N{GREEK SMALL LETTER LAMDA}o>world</hel\N{GREEK SMALL LETTER LAMDA}o>")
157        h = hello.parse(u"<?xml version='1.0' encoding='utf-16' ?><hel\N{GREEK SMALL LETTER LAMDA}o>world</hel\N{GREEK SMALL LETTER LAMDA}o>")
158        self.assertTrue(h)
159
160        h = hello()
161        self.assertEqual(h.render(),u'<?xml version="1.0" ?><hel\N{GREEK SMALL LETTER LAMDA}o />')
162        self.assertEqual(h.render(fragment=True),u"<hel\N{GREEK SMALL LETTER LAMDA}o />")
163        self.assertEqual(h.render(encoding="utf8"),u'<?xml version="1.0" encoding="utf8" ?><hel\N{GREEK SMALL LETTER LAMDA}o />'.encode("utf8"))
164        self.assertEqual(h.render(encoding="utf8",fragment=True),u"<hel\N{GREEK SMALL LETTER LAMDA}o />".encode("utf8"))
165
166        self.assertEqual(h.render(),"".join(h.irender()))
167        self.assertEqual(h.render(fragment=True),"".join(h.irender(fragment=True)))
168        self.assertEqual(h.render(encoding="utf8"),b("").join(h.irender(encoding="utf8")))
169        self.assertEqual(h.render(encoding="utf8",fragment=True),b("").join(h.irender(encoding="utf8",fragment=True)))
170
171    def test_unicode_string_field(self):
172        """Test a dexml.Model class with a unicode string field."""
173        class Person(dexml2.Model):
174            name = fields.String()
175
176        p = Person.parse(u"<Person name='hel\N{GREEK SMALL LETTER LAMDA}o'/>")
177        self.assertEqual(p.name, u"hel\N{GREEK SMALL LETTER LAMDA}o")
178
179        p = Person()
180        p.name = u"hel\N{GREEK SMALL LETTER LAMDA}o"
181        self.assertEqual(p.render(encoding="utf8"), u'<?xml version="1.0" encoding="utf8" ?><Person name="hel\N{GREEK SMALL LETTER LAMDA}o" />'.encode("utf8"))
182
183    def test_model_meta_attributes(self):
184        class hello(dexml2.Model):
185            pass
186
187        self.assertRaises(dexml2.ParseError, hello.parse, "<Hello />")
188        hello.meta.case_sensitive = False
189        self.assertTrue(hello.parse("<Hello />"))
190        self.assertRaises(dexml2.ParseError, hello.parse, "<Helpo />")
191        hello.meta.case_sensitive = True
192
193        self.assertTrue(hello.parse("<hello>world</hello>"))
194        hello.meta.ignore_unknown_elements = False
195        self.assertRaises(dexml2.ParseError, hello.parse, "<hello>world</hello>")
196        hello.meta.ignore_unknown_elements = True
197
198
199    def test_namespace(self):
200        """Test basic handling of namespaces."""
201        class hello(dexml2.Model):
202            class meta:
203                namespace = "http://hello.com/"
204                ignore_unknown_elements = False
205
206        h = hello.parse("<hello xmlns='http://hello.com/' />")
207        self.assertTrue(h)
208
209        h = hello.parse("<H:hello xmlns:H='http://hello.com/' />")
210        self.assertTrue(h)
211
212        self.assertRaises(dexml2.ParseError, hello.parse, "<hello />")
213        self.assertRaises(dexml2.ParseError, hello.parse, "<H:hllo xmlns:H='http://hello.com/' />")
214        self.assertRaises(dexml2.ParseError, hello.parse, "<H:hello xmlns:H='http://hello.com/'>world</H:hello>")
215
216        hello.meta.case_sensitive = False
217        self.assertRaises(dexml2.ParseError, hello.parse, "<Hello />")
218        self.assertRaises(dexml2.ParseError, hello.parse, "<H:hllo xmlns:H='http://hello.com/' />")
219        self.assertRaises(dexml2.ParseError, hello.parse, "<H:hello xmlns:H='http://Hello.com/' />")
220        hello.parse("<H:HeLLo xmlns:H='http://hello.com/' />")
221        hello.meta.case_sensitive = True
222
223        h = hello()
224        self.assertEqual(h.render(fragment=True),'<hello xmlns="http://hello.com/" />')
225
226        hello.meta.namespace_prefix = "H"
227        self.assertEqual(h.render(fragment=True),'<H:hello xmlns:H="http://hello.com/" />')
228
229
230
231    def test_base_field(self):
232        """Test operation of the base Field class (for coverage purposes)."""
233        class tester(dexml2.Model):
234            value = fields.Field()
235        assert isinstance(tester.value,fields.Field)
236        #  This is a parse error because Field doesn't consume any nodes
237        self.assertRaises(dexml2.ParseError, tester.parse, "<tester value='42' />")
238        self.assertRaises(dexml2.ParseError, tester.parse, "<tester><value>42</value></tester>")
239        #  Likewise, Field doesn't output any XML so it thinks value is missing
240        self.assertRaises(dexml2.RenderError, tester(value=None).render)
241
242
243    def test_value_fields(self):
244        """Test operation of basic value fields."""
245        class hello(dexml2.Model):
246            recipient = fields.String()
247            sentby = fields.String(attrname="sender")
248            strength = fields.Integer(default=1)
249            message = fields.String(tagname="msg")
250
251        h = hello.parse("<hello recipient='ryan' sender='lozz' strength='7'><msg>hi there</msg></hello>")
252        self.assertEqual(h.recipient,"ryan")
253        self.assertEqual(h.sentby,"lozz")
254        self.assertEqual(h.message,"hi there")
255        self.assertEqual(h.strength,7)
256
257        #  These are parse errors due to namespace mismatches
258        self.assertRaises(dexml2.ParseError, hello.parse, "<hello xmlns:N='N:' N:recipient='ryan' sender='lozz' strength='7'><msg>hi there</msg></hello>")
259        self.assertRaises(dexml2.ParseError, hello.parse, "<hello xmlns:N='N:' recipient='ryan' sender='lozz' strength='7'><N:msg>hi there</N:msg></hello>")
260
261        #  These are parse errors due to subtags
262        self.assertRaises(dexml2.ParseError, hello.parse, "<hello recipient='ryan' sender='lozz' strength='7'><msg>hi <b>there</b></msg></hello>")
263
264
265    def test_float_field(self):
266        class F(dexml2.Model):
267            value = fields.Float()
268        self.assertEqual(F.parse("<F value='4.2' />").value,4.2)
269
270
271    def test_boolean_field(self):
272        class F(dexml2.Model):
273            value = fields.Boolean()
274        self.assertTrue(F.parse("<F value='' />").value)
275        self.assertTrue(F.parse("<F value='on' />").value)
276        self.assertTrue(F.parse("<F value='YeS' />").value)
277        self.assertFalse(F.parse("<F value='off' />").value)
278        self.assertFalse(F.parse("<F value='no' />").value)
279        self.assertFalse(F.parse("<F value='FaLsE' />").value)
280
281        f = F.parse("<F value='' />")
282        assert model_fields_equal(F.parse(f.render()),f)
283        f.value = "someotherthing"
284        assert model_fields_equal(F.parse(f.render()),f)
285        f.value = False
286        assert model_fields_equal(F.parse(f.render()),f)
287
288
289    def test_string_with_special_chars(self):
290        class letter(dexml2.Model):
291            message = fields.String(tagname="msg")
292
293        l = letter.parse("<letter><msg>hello &amp; goodbye</msg></letter>")
294        self.assertEqual(l.message,"hello & goodbye")
295        l = letter.parse("<letter><msg><![CDATA[hello & goodbye]]></msg></letter>")
296        self.assertEqual(l.message,"hello & goodbye")
297
298        l = letter(message="XML <tags> are fun!")
299        self.assertEqual(l.render(fragment=True),'<letter><msg>XML &lt;tags&gt; are fun!</msg></letter>')
300
301        class update(dexml2.Model):
302            status = fields.String(attrname="status")
303
304        u = update(status="feeling <awesome>!")
305        self.assertEqual(u.render(fragment=True),'<update status="feeling &lt;awesome&gt;!" />')
306
307
308    def test_cdata_fields(self):
309        try:
310            class update(dexml2.Model):
311                status = fields.CDATA()
312            assert False, "CDATA allowed itself to be created without tagname"
313        except ValueError:
314            pass
315        class update(dexml2.Model):
316            status = fields.CDATA(tagname=True)
317        u = update(status="feeling <awesome>!")
318        self.assertEqual(u.render(fragment=True),'<update><status><![CDATA[feeling <awesome>!]]></status></update>')
319
320
321    def test_model_field(self):
322        """Test operation of fields.Model."""
323        class person(dexml2.Model):
324            name = fields.String()
325            age = fields.Integer()
326        class pet(dexml2.Model):
327            name = fields.String()
328            species = fields.String(required=False)
329        class Vet(dexml2.Model):
330            class meta:
331                tagname = "vet"
332            name = fields.String()
333        class pets(dexml2.Model):
334            person = fields.Model()
335            pet1 = fields.Model("pet")
336            pet2 = fields.Model(pet,required=False)
337            pet3 = fields.Model((None,pet),required=False)
338            vet = fields.Model((None,"Vet"),required=False)
339
340        p = pets.parse("<pets><person name='ryan' age='26'/><pet name='riley' species='dog' /></pets>")
341        self.assertEqual(p.person.name,"ryan")
342        self.assertEqual(p.pet1.species,"dog")
343        self.assertEqual(p.pet2,None)
344
345        p = pets.parse("<pets>\n<person name='ryan' age='26'/>\n<pet name='riley' species='dog' />\n<pet name='fishy' species='fish' />\n</pets>")
346        self.assertEqual(p.person.name,"ryan")
347        self.assertEqual(p.pet1.name,"riley")
348        self.assertEqual(p.pet2.species,"fish")
349
350        p = pets.parse("<pets><person name='ryan' age='26'/><pet name='riley' species='dog' /><pet name='fishy' species='fish' /><pet name='meowth' species='cat' /><vet name='Nic' /></pets>")
351        self.assertEqual(p.person.name,"ryan")
352        self.assertEqual(p.pet1.name,"riley")
353        self.assertEqual(p.pet2.species,"fish")
354        self.assertEqual(p.pet3.species,"cat")
355        self.assertEqual(p.vet.name,"Nic")
356
357        self.assertRaises(dexml2.ParseError, pets.parse, "<pets><pet name='riley' species='fish' /></pets>")
358        self.assertRaises(dexml2.ParseError, pets.parse, "<pets><person name='riley' age='2' /></pets>")
359
360        def assign(val):
361            p.pet1 = val
362        self.assertRaises(ValueError, assign, person(name = 'ryan', age = 26))
363        self.assertEqual(p.pet1.name,"riley")
364        assign(pet(name="spike"))
365        self.assertEqual(p.pet1.name,"spike")
366
367        p = pets()
368        self.assertRaises(dexml2.RenderError, p.render)
369        p.person = person(name="lozz",age="25")
370        p.pet1 = pet(name="riley")
371        self.assertEqual(p.render(fragment=True),'<pets><person name="lozz" age="25" /><pet name="riley" /></pets>')
372        self.assertEqual("".join(p.irender(fragment=True)),'<pets><person name="lozz" age="25" /><pet name="riley" /></pets>')
373        p.pet2 = pet(name="guppy",species="fish")
374        self.assertEqual(p.render(fragment=True),'<pets><person name="lozz" age="25" /><pet name="riley" /><pet name="guppy" species="fish" /></pets>')
375        self.assertEqual("".join(p.irender(fragment=True)),'<pets><person name="lozz" age="25" /><pet name="riley" /><pet name="guppy" species="fish" /></pets>')
376
377
378    def test_model_field_namespace(self):
379        """Test operation of fields.Model with namespaces"""
380        class petbase(dexml2.Model):
381            class meta:
382                namespace = "http://www.pets.com/PetML"
383                namespace_prefix = "P"
384        class person(petbase):
385            name = fields.String()
386            age = fields.Integer()
387            status = fields.String(tagname=("S:","status"),required=False)
388        class pet(petbase):
389            name = fields.String()
390            species = fields.String(required=False)
391        class pets(petbase):
392            person = fields.Model()
393            pet1 = fields.Model("pet")
394            pet2 = fields.Model(pet,required=False)
395
396        p = pets.parse("<pets xmlns='http://www.pets.com/PetML'><person name='ryan' age='26'/><pet name='riley' species='dog' /></pets>")
397        self.assertEqual(p.person.name,"ryan")
398        self.assertEqual(p.pet1.species,"dog")
399        self.assertEqual(p.pet2,None)
400
401        p = pets.parse("<P:pets xmlns:P='http://www.pets.com/PetML'><P:person name='ryan' age='26'/><P:pet name='riley' species='dog' /><P:pet name='fishy' species='fish' /></P:pets>")
402        self.assertEqual(p.person.name,"ryan")
403        self.assertEqual(p.pet1.name,"riley")
404        self.assertEqual(p.pet2.species,"fish")
405
406        self.assertRaises(dexml2.ParseError, pets.parse, "<pets><pet name='riley' species='fish' /></pets>")
407        self.assertRaises(dexml2.ParseError, pets.parse, "<pets><person name='riley' age='2' /></pets>")
408
409        p = pets()
410        self.assertRaises(dexml2.RenderError, p.render)
411
412        p.person = person(name="lozz",age="25")
413        p.pet1 = pet(name="riley")
414        self.assertEqual(p.render(fragment=True),'<P:pets xmlns:P="http://www.pets.com/PetML"><P:person name="lozz" age="25" /><P:pet name="riley" /></P:pets>')
415
416        p.pet2 = pet(name="guppy",species="fish")
417        self.assertEqual(p.render(fragment=True),'<P:pets xmlns:P="http://www.pets.com/PetML"><P:person name="lozz" age="25" /><P:pet name="riley" /><P:pet name="guppy" species="fish" /></P:pets>')
418
419        p = person.parse('<P:person xmlns:P="http://www.pets.com/PetML" name="ryan" age="26"><status>awesome</status></P:person>')
420        self.assertEqual(p.status,None)
421        p = person.parse('<P:person xmlns:P="http://www.pets.com/PetML" name="ryan" age="26"><P:status>awesome</P:status></P:person>')
422        self.assertEqual(p.status,None)
423        p = person.parse('<P:person xmlns:P="http://www.pets.com/PetML" xmlns:S="S:" name="ryan" age="26"><S:sts>awesome</S:sts></P:person>')
424        self.assertEqual(p.status,None)
425        p = person.parse('<P:person xmlns:P="http://www.pets.com/PetML" xmlns:S="S:" name="ryan" age="26"><S:status>awesome</S:status></P:person>')
426        self.assertEqual(p.status,"awesome")
427
428
429    def test_list_field(self):
430        """Test operation of fields.List"""
431        class person(dexml2.Model):
432            name = fields.String()
433            age = fields.Integer()
434        class pet(dexml2.Model):
435            name = fields.String()
436            species = fields.String(required=False)
437        class reward(dexml2.Model):
438            date = fields.String()
439        class pets(dexml2.Model):
440            person = fields.Model()
441            pets = fields.List("pet",minlength=1)
442            notes = fields.List(fields.String(tagname="note"),maxlength=2)
443            rewards = fields.List("reward",tagname="rewards",required=False)
444
445        p = pets.parse("<pets><person name='ryan' age='26'/><pet name='riley' species='dog' /></pets>")
446        self.assertEqual(p.person.name,"ryan")
447        self.assertEqual(p.pets[0].species,"dog")
448        self.assertEqual(len(p.pets),1)
449        self.assertEqual(len(p.notes),0)
450
451        p = pets.parse("<pets>\n\t<person name='ryan' age='26'/>\n\t<pet name='riley' species='dog' />\n\t<pet name='fishy' species='fish' />\n\t<note>noted</note></pets>")
452        self.assertEqual(p.person.name,"ryan")
453        self.assertEqual(p.pets[0].name,"riley")
454        self.assertEqual(p.pets[1].species,"fish")
455        self.assertEqual(p.notes[0],"noted")
456        self.assertEqual(len(p.pets),2)
457        self.assertEqual(len(p.notes),1)
458
459        self.assertRaises(dexml2.ParseError, pets.parse, "<pets><pet name='riley' species='fish' /></pets>")
460        self.assertRaises(dexml2.ParseError, pets.parse, "<pets><person name='ryan' age='26' /></pets>")
461        self.assertRaises(dexml2.ParseError, pets.parse, "<pets><person name='ryan' age='26'/><pet name='riley' species='dog' /><note>too</note><note>many</note><note>notes</note></pets>")
462
463        p = pets()
464        p.person = person(name="lozz",age="25")
465        self.assertRaises(dexml2.RenderError, p.render)
466
467        p.pets.append(pet(name="riley"))
468        self.assertEqual(p.render(fragment=True),'<pets><person name="lozz" age="25" /><pet name="riley" /></pets>')
469
470        p.pets.append(pet(name="guppy",species="fish"))
471        p.notes.append("noted")
472        self.assertEqual(p.render(fragment=True),'<pets><person name="lozz" age="25" /><pet name="riley" /><pet name="guppy" species="fish" /><note>noted</note></pets>')
473
474        p = pets()
475        p.person = person(name="lozz",age="25")
476        yielded_items = []
477        def gen_pets():
478            for p in (pet(name="riley"),pet(name="guppy",species="fish")):
479                yielded_items.append(p)
480                yield p
481        p.pets = gen_pets()
482        self.assertEqual(len(yielded_items),0)
483        p.notes.append("noted")
484        self.assertEqual(p.render(fragment=True),'<pets><person name="lozz" age="25" /><pet name="riley" /><pet name="guppy" species="fish" /><note>noted</note></pets>')
485        self.assertEqual(len(yielded_items),2)
486
487        p = pets.parse("<pets><person name='ryan' age='26'/><pet name='riley' species='dog' /><rewards><reward date='February 23, 2010'/><reward date='November 10, 2009'/></rewards></pets>")
488        self.assertEqual(len(p.rewards), 2)
489        self.assertEqual(p.rewards[1].date, 'November 10, 2009')
490        self.assertEqual(p.render(fragment = True), '<pets><person name="ryan" age="26" /><pet name="riley" species="dog" /><rewards><reward date="February 23, 2010" /><reward date="November 10, 2009" /></rewards></pets>')
491
492        pets.meta.ignore_unknown_elements = False
493        self.assertRaises(dexml2.ParseError, pets.parse, "<pets><person name='ryan' age='26' /><pet name='riley' species='dog' /><reward date='February 23, 2010'/><reward date='November 10, 2009' /></pets>")
494
495    def test_list_field_tagname(self):
496        """Test List(tagname="items",required=True)."""
497        class obj(dexml2.Model):
498            items = fields.List(fields.String(tagname="item"),tagname="items")
499        o = obj(items=[])
500        self.assertEqual(o.render(fragment=True), '<obj><items /></obj>')
501        self.assertRaises(dexml2.ParseError, obj.parse, '<obj />')
502        o = obj.parse('<obj><items /></obj>')
503        self.assertEqual(o.items,[])
504
505    def test_list_field_sanity_checks(self):
506        class GreedyField(fields.Field):
507            def parse_child_node(self,obj,node):
508                return dexml2.PARSE_MORE
509        class SaneList(dexml2.Model):
510            item = fields.List(GreedyField(tagname="item"))
511        self.assertRaises(ValueError,SaneList.parse,"<SaneList><item /><item /></SaneList>")
512
513
514    def test_list_field_max_min(self):
515        try:
516            class MyStuff(dexml2.Model):
517                items = fields.List(fields.String(tagname="item"),required=False,minlength=2)
518            assert False, "List allowed creation with nonsensical args"
519        except ValueError:
520            pass
521
522        class MyStuff(dexml2.Model):
523            items = fields.List(fields.String(tagname="item"),required=False)
524        self.assertEqual(MyStuff.parse("<MyStuff />").items,[])
525
526        MyStuff.items.maxlength = 1
527        self.assertEqual(MyStuff.parse("<MyStuff><item /></MyStuff>").items,[""])
528        self.assertRaises(dexml2.ParseError, MyStuff.parse, "<MyStuff><item /><item /></MyStuff>")
529        s = MyStuff()
530        s.items = ["one","two"]
531        self.assertRaises(dexml2.RenderError, s.render)
532
533        MyStuff.items.maxlength = None
534        MyStuff.items.minlength = 2
535        MyStuff.items.required = True
536        self.assertEqual(MyStuff.parse("<MyStuff><item /><item /></MyStuff>").items,["",""])
537        self.assertRaises(dexml2.ParseError, MyStuff.parse, "<MyStuff><item /></MyStuff>")
538
539
540    def test_dict_field(self):
541        """Test operation of fields.Dict"""
542        class item(dexml2.Model):
543            name = fields.String()
544            attr = fields.String(tagname = 'attr')
545        class obj(dexml2.Model):
546            items = fields.Dict('item', key = 'name')
547
548        xml = '<obj><item name="item1"><attr>val1</attr></item><item name="item2"><attr>val2</attr></item></obj>'
549        o = obj.parse(xml)
550        self.assertEqual(len(o.items), 2)
551        self.assertEqual(o.items['item1'].name, 'item1')
552        self.assertEqual(o.items['item2'].attr, 'val2')
553        del o.items['item2']
554        self.assertEqual(o.render(fragment = True), '<obj><item name="item1"><attr>val1</attr></item></obj>')
555
556        o.items['item3'] = item(attr = 'val3')
557        self.assertEqual(o.items['item3'].attr, 'val3')
558        def _setitem():
559            o.items['item3'] = item(name = 'item2', attr = 'val3')
560        self.assertRaises(ValueError, _setitem)
561
562        class obj(dexml2.Model):
563            items = fields.Dict(fields.Model(item), key = 'name', unique = True)
564        xml = '<obj><item name="item1"><attr>val1</attr></item><item name="item1"><attr>val2</attr></item></obj>'
565        self.assertRaises(dexml2.ParseError, obj.parse, xml)
566
567        class obj(dexml2.Model):
568            items = fields.Dict('item', key = 'name', tagname = 'items')
569        xml = '<obj> <ignoreme /> <items> <item name="item1"><attr>val1</attr></item> <item name="item2"><attr>val2</attr></item> </items> </obj>'
570
571        o = obj.parse(xml)
572        self.assertEqual(len(o.items), 2)
573        self.assertEqual(o.items['item1'].name, 'item1')
574        self.assertEqual(o.items['item2'].attr, 'val2')
575        del o.items['item2']
576        self.assertEqual(o.render(fragment = True), '<obj><items><item name="item1"><attr>val1</attr></item></items></obj>')
577
578        # Test that wrapper tags are still required even for empty fields
579        o = obj(items={})
580        self.assertEqual(o.render(fragment=True), '<obj><items /></obj>')
581        o = obj.parse('<obj><items /></obj>')
582        self.assertEqual(o.items,{})
583        self.assertRaises(dexml2.ParseError, obj.parse, '<obj />')
584        obj.items.required = False
585        self.assertEqual(o.render(fragment=True), '<obj />')
586        obj.items.required = True
587
588        from collections import defaultdict
589        class _dict(defaultdict):
590            def __init__(self):
591                super(_dict, self).__init__(item)
592
593        class obj(dexml2.Model):
594            items = fields.Dict('item', key = 'name', dictclass = _dict)
595        o = obj()
596        self.assertEqual(o.items['item1'].name, 'item1')
597
598
599    def test_dict_field_sanity_checks(self):
600        class GreedyField(fields.Field):
601            def parse_child_node(self,obj,node):
602                return dexml2.PARSE_MORE
603        class SaneDict(dexml2.Model):
604            item = fields.Dict(GreedyField(tagname="item"),key="name")
605        self.assertRaises(ValueError,SaneDict.parse,"<SaneDict><item /></SaneDict>")
606
607        class item(dexml2.Model):
608            name = fields.String()
609            value = fields.String()
610        class MyStuff(dexml2.Model):
611            items = fields.Dict(item,key="wrongname")
612        self.assertRaises(dexml2.ParseError, MyStuff.parse, "<MyStuff><ignoreme /><item name='hi' value='world' /></MyStuff>")
613
614
615    def test_dict_field_max_min(self):
616        class item(dexml2.Model):
617            name = fields.String()
618            value = fields.String()
619        try:
620            class MyStuff(dexml2.Model):
621                items = fields.Dict(item,key="name",required=False,minlength=2)
622            assert False, "Dict allowed creation with nonsensical args"
623        except ValueError:
624            pass
625
626        class MyStuff(dexml2.Model):
627            items = fields.Dict(item,key="name",required=False)
628        self.assertEqual(MyStuff.parse("<MyStuff />").items,{})
629
630        MyStuff.items.maxlength = 1
631        self.assertEqual(len(MyStuff.parse("<MyStuff><item name='hi' value='world' /></MyStuff>").items),1)
632        self.assertRaises(dexml2.ParseError, MyStuff.parse, "<MyStuff><item name='hi' value='world' /><item name='hello' value='earth' /></MyStuff>")
633        s = MyStuff()
634        s.items = [item(name="yo",value="dawg"),item(name="wazzup",value="yo")]
635        self.assertRaises(dexml2.RenderError, s.render)
636
637        MyStuff.items.maxlength = None
638        MyStuff.items.minlength = 2
639        MyStuff.items.required = True
640        self.assertEqual(len(MyStuff.parse("<MyStuff><item name='hi' value='world' /><item name='hello' value='earth' /></MyStuff>").items),2)
641        self.assertRaises(dexml2.ParseError, MyStuff.parse, "<MyStuff><item name='hi' value='world' /></MyStuff>")
642
643        s = MyStuff()
644        s.items = [item(name="yo",value="dawg")]
645        self.assertRaises(dexml2.RenderError, s.render)
646
647
648    def test_choice_field(self):
649        """Test operation of fields.Choice"""
650        class breakfast(dexml2.Model):
651            meal = fields.Choice("bacon","cereal")
652        class bacon(dexml2.Model):
653            num_rashers = fields.Integer()
654        class cereal(dexml2.Model):
655            with_milk = fields.Boolean()
656
657        b = breakfast.parse("<breakfast><bacon num_rashers='4' /></breakfast>")
658        self.assertEqual(b.meal.num_rashers,4)
659
660        b = breakfast.parse("<breakfast><cereal with_milk='true' /></breakfast>")
661        self.assertTrue(b.meal.with_milk)
662
663        self.assertRaises(dexml2.ParseError, b.parse, "<breakfast><eggs num='2' /></breakfast>")
664        self.assertRaises(dexml2.ParseError, b.parse, "<breakfast />")
665
666        b = breakfast()
667        self.assertRaises(dexml2.RenderError, b.render)
668        b.meal = bacon(num_rashers=1)
669        self.assertEqual(b.render(fragment=True),"<breakfast><bacon num_rashers=\"1\" /></breakfast>")
670
671
672    def test_choice_field_sanity_checks(self):
673        try:
674            class SaneChoice(dexml2.Model):
675                item = fields.Choice(fields.String(),fields.Integer())
676            assert False, "Choice field failed its sanity checks"
677        except ValueError:
678            pass
679        class GreedyModel(fields.Model):
680            def parse_child_node(self,obj,node):
681                return dexml2.PARSE_MORE
682        class SaneChoice(dexml2.Model):
683            item = fields.Choice(GreedyModel("SaneChoice"))
684
685        self.assertRaises(ValueError,SaneChoice.parse,"<SaneChoice><SaneChoice /></SaneChoice>")
686
687
688    def test_list_of_choice(self):
689        """Test operation of fields.Choice inside fields.List"""
690        class breakfast(dexml2.Model):
691            meals = fields.List(fields.Choice("bacon","cereal"))
692        class bacon(dexml2.Model):
693            num_rashers = fields.Integer()
694        class cereal(dexml2.Model):
695            with_milk = fields.Boolean()
696
697        b = breakfast.parse("<breakfast><bacon num_rashers='4' /></breakfast>")
698        self.assertEqual(len(b.meals),1)
699        self.assertEqual(b.meals[0].num_rashers,4)
700
701        b = breakfast.parse("<breakfast><bacon num_rashers='2' /><cereal with_milk='true' /></breakfast>")
702        self.assertEqual(len(b.meals),2)
703        self.assertEqual(b.meals[0].num_rashers,2)
704        self.assertTrue(b.meals[1].with_milk)
705
706
707    def test_empty_only_boolean(self):
708        """Test operation of fields.Boolean with empty_only=True"""
709        class toggles(dexml2.Model):
710            toggle_str = fields.Boolean(required=False)
711            toggle_empty = fields.Boolean(tagname=True,empty_only=True)
712
713        t = toggles.parse("<toggles />")
714        self.assertFalse(t.toggle_str)
715        self.assertFalse(t.toggle_empty)
716
717        t = toggles.parse("<toggles toggle_str=''><toggle_empty /></toggles>")
718        self.assertTrue(t.toggle_str)
719        self.assertTrue(t.toggle_empty)
720
721        t = toggles.parse("<toggles toggle_str='no'><toggle_empty /></toggles>")
722        self.assertFalse(t.toggle_str)
723        self.assertTrue(t.toggle_empty)
724
725        self.assertRaises(ValueError,toggles.parse,"<toggles><toggle_empty>no</toggle_empty></toggles>")
726        self.assertFalse("toggle_empty" in toggles(toggle_empty=False).render())
727        self.assertTrue("<toggle_empty />" in toggles(toggle_empty=True).render())
728
729    def test_XmlNode(self):
730        """Test correct operation of fields.XmlNode."""
731        class bucket(dexml2.Model):
732            class meta:
733                namespace = "bucket-uri"
734            contents = fields.XmlNode(encoding="utf8")
735        b = bucket.parse("<B:bucket xmlns:B='bucket-uri'><B:contents><hello><B:world /></hello></B:contents></B:bucket>")
736        self.assertEqual(b.contents.childNodes[0].tagName,"hello")
737        self.assertEqual(b.contents.childNodes[0].namespaceURI,None)
738        self.assertEqual(b.contents.childNodes[0].childNodes[0].localName,"world")
739        self.assertEqual(b.contents.childNodes[0].childNodes[0].namespaceURI,"bucket-uri")
740
741        b = bucket()
742        b.contents = "<hello>world</hello>"
743        b = bucket.parse(b.render())
744        self.assertEqual(b.contents.tagName,"hello")
745        b.contents = u"<hello>world</hello>"
746        b = bucket.parse(b.render())
747        self.assertEqual(b.contents.tagName,"hello")
748
749        b = bucket.parse("<bucket xmlns='bucket-uri'><bucket><hello /></bucket></bucket>")
750        b2 = bucket.parse("".join(fields.XmlNode.render_children(b,b.contents,{})))
751        self.assertEqual(b2.contents.tagName,"hello")
752
753        class bucket(dexml2.Model):
754            class meta:
755                namespace = "bucket-uri"
756            contents = fields.XmlNode(tagname="contents")
757        b = bucket.parse("<B:bucket xmlns:B='bucket-uri'><ignoreme /><B:contents><hello><B:world /></hello></B:contents></B:bucket>")
758        self.assertEqual(b.contents.childNodes[0].tagName,"hello")
759
760
761    def test_namespaced_attrs(self):
762        class nsa(dexml2.Model):
763            f1 = fields.Integer(attrname=("test:","f1"))
764        n = nsa.parse("<nsa t:f1='7' xmlns:t='test:' />")
765        self.assertEqual(n.f1,7)
766        n2 = nsa.parse(n.render())
767        self.assertEqual(n2.f1,7)
768
769        class nsa_decl(dexml2.Model):
770            class meta:
771                tagname = "nsa"
772                namespace = "test:"
773                namespace_prefix = "t"
774            f1 = fields.Integer(attrname=("test:","f1"))
775        n = nsa_decl.parse("<t:nsa t:f1='7' xmlns:t='test:' />")
776        self.assertEqual(n.f1,7)
777        self.assertEqual(n.render(fragment=True),'<t:nsa xmlns:t="test:" t:f1="7" />')
778
779
780    def test_namespaced_children(self):
781        class nsc(dexml2.Model):
782            f1 = fields.Integer(tagname=("test:","f1"))
783        n = nsc.parse("<nsc xmlns:t='test:'><t:f1>7</t:f1></nsc>")
784        self.assertEqual(n.f1,7)
785        n2 = nsc.parse(n.render())
786        self.assertEqual(n2.f1,7)
787
788        n = nsc.parse("<nsc><f1 xmlns='test:'>7</f1></nsc>")
789        self.assertEqual(n.f1,7)
790        n2 = nsc.parse(n.render())
791        self.assertEqual(n2.f1,7)
792
793        class nsc_decl(dexml2.Model):
794            class meta:
795                tagname = "nsc"
796                namespace = "test:"
797                namespace_prefix = "t"
798            f1 = fields.Integer(tagname=("test:","f1"))
799        n = nsc_decl.parse("<t:nsc xmlns:t='test:'><t:f1>7</t:f1></t:nsc>")
800        self.assertEqual(n.f1,7)
801        n2 = nsc_decl.parse(n.render())
802        self.assertEqual(n2.f1,7)
803
804        n = nsc_decl.parse("<nsc xmlns='test:'><f1>7</f1></nsc>")
805        self.assertEqual(n.f1,7)
806        n2 = nsc_decl.parse(n.render())
807        self.assertEqual(n2.f1,7)
808
809        self.assertEqual(n2.render(fragment=True),'<t:nsc xmlns:t="test:"><t:f1>7</t:f1></t:nsc>')
810
811
812    def test_order_sensitive(self):
813        """Test operation of order-sensitive and order-insensitive parsing"""
814        class junk(dexml2.Model):
815            class meta:
816                order_sensitive = True
817            name = fields.String(tagname=True)
818            notes = fields.List(fields.String(tagname="note"))
819            amount = fields.Integer(tagname=True)
820        class junk_unordered(junk):
821            class meta:
822                tagname = "junk"
823                order_sensitive = False
824
825        j = junk.parse("<junk><name>test1</name><note>note1</note><note>note2</note><amount>7</amount></junk>")
826        self.assertEqual(j.name,"test1")
827        self.assertEqual(j.notes,["note1","note2"])
828        self.assertEqual(j.amount,7)
829
830        j = junk_unordered.parse("<junk><name>test1</name><note>note1</note><note>note2</note><amount>7</amount></junk>")
831        self.assertEqual(j.name,"test1")
832        self.assertEqual(j.notes,["note1","note2"])
833        self.assertEqual(j.amount,7)
834
835        self.assertRaises(dexml2.ParseError, junk.parse, "<junk><note>note1</note><amount>7</amount><note>note2</note><name>test1</name></junk>")
836
837        j = junk_unordered.parse("<junk><note>note1</note><amount>7</amount><note>note2</note><name>test1</name></junk>")
838        self.assertEqual(j.name,"test1")
839        self.assertEqual(j.notes,["note1","note2"])
840        self.assertEqual(j.amount,7)
841
842
843    def test_namespace_prefix_generation(self):
844        class A(dexml2.Model):
845            class meta:
846                namespace='http://xxx'
847            a = fields.String(tagname=('http://yyy','a'))
848        class B(dexml2.Model):
849            class meta:
850                namespace='http://yyy'
851            b = fields.Model(A)
852
853        b1 = B(b=A(a='value'))
854
855        #  With no specific prefixes set we can't predict the output,
856        #  but it should round-trip OK.
857        assert model_fields_equal(B.parse(b1.render()),b1)
858
859        #  With specific prefixes set, output is predictable.
860        A.meta.namespace_prefix = "x"
861        B.meta.namespace_prefix = "y"
862        self.assertEqual(b1.render(),'<?xml version="1.0" ?><y:B xmlns:y="http://yyy"><x:A xmlns:x="http://xxx"><y:a>value</y:a></x:A></y:B>')
863        A.meta.namespace_prefix = None
864        B.meta.namespace_prefix = None
865
866        #  This is a little hackery to trick the random-prefix generator
867        #  into looping a few times before picking one.  We can't predict
868        #  the output but it'll exercise the code.
869        class pickydict(dict):
870            def __init__(self,*args,**kwds):
871                self.__counter = 0
872                super(pickydict,self).__init__(*args,**kwds)
873            def __contains__(self,key):
874                if self.__counter > 5:
875                    return super(pickydict,self).__contains__(key)
876                self.__counter += 1
877                return True
878        assert model_fields_equal(B.parse(b1.render(nsmap=pickydict())),b1)
879
880        class A(dexml2.Model):
881            class meta:
882                namespace='T:'
883            a = fields.String(attrname=('A:','a'))
884            b = fields.String(attrname=(None,'b'))
885            c = fields.String(tagname=(None,'c'))
886
887        a1 = A(a="hello",b="world",c="owyagarn")
888
889        #  With no specific prefixes set we can't predict the output,
890        #  but it should round-trip OK.
891        assert model_fields_equal(A.parse(a1.render()),a1)
892
893        #  With specific prefixes set, output is predictable.
894        #  Note that this suppresses generation of the xmlns declarations,
895        #  so the output is actually broken here.  Broken, but predictable.
896        nsmap = {}
897        nsmap["T"] = ["T:"]
898        nsmap["A"] = ["A:"]
899        self.assertEqual(a1.render(fragment=True,nsmap=nsmap),'<A xmlns="T:" A:a="hello" b="world"><c xmlns="">owyagarn</c></A>')
900
901        #  This is a little hackery to trick the random-prefix generator
902        #  into looping a few times before picking one.  We can't predict
903        #  the output but it'll exercise the code.
904        class pickydict(dict):
905            def __init__(self,*args,**kwds):
906                self.__counter = 0
907                super(pickydict,self).__init__(*args,**kwds)
908            def __contains__(self,key):
909                if self.__counter > 5:
910                    return super(pickydict,self).__contains__(key)
911                self.__counter += 1
912                return True
913        assert model_fields_equal(A.parse(a1.render(nsmap=pickydict())),a1)
914
915        A.c.tagname = ("C:","c")
916        assert model_fields_equal(A.parse(a1.render(nsmap=pickydict())),a1)
917        a1 = A(a="hello",b="world",c="")
918        assert model_fields_equal(A.parse(a1.render(nsmap=pickydict())),a1)
919
920
921    def test_parsing_value_from_tag_contents(self):
922        class attr(dexml2.Model):
923            name = fields.String()
924            value = fields.String(tagname=".")
925        class obj(dexml2.Model):
926            id = fields.String()
927            attrs = fields.List(attr)
928        o = obj.parse('<obj id="z108"><attr name="level">6</attr><attr name="descr">description</attr></obj>')
929        self.assertEqual(o.id,"z108")
930        self.assertEqual(len(o.attrs),2)
931        self.assertEqual(o.attrs[0].name,"level")
932        self.assertEqual(o.attrs[0].value,"6")
933        self.assertEqual(o.attrs[1].name,"descr")
934        self.assertEqual(o.attrs[1].value,"description")
935
936        o = obj(id="test")
937        o.attrs.append(attr(name="hello",value="world"))
938        o.attrs.append(attr(name="wherethe",value="bloodyhellareya"))
939        self.assertEqual(o.render(fragment=True),'<obj id="test"><attr name="hello">world</attr><attr name="wherethe">bloodyhellareya</attr></obj>')
940
941
942    def test_inheritance_of_meta_attributes(self):
943        class Base1(dexml2.Model):
944            class meta:
945                tagname = "base1"
946                order_sensitive = True
947        class Base2(dexml2.Model):
948            class meta:
949                tagname = "base2"
950                order_sensitive = False
951
952        class Sub(Base1):
953            pass
954        self.assertEqual(Sub.meta.order_sensitive,True)
955
956        class Sub(Base2):
957            pass
958        self.assertEqual(Sub.meta.order_sensitive,False)
959
960        class Sub(Base2):
961            class meta:
962                order_sensitive = True
963        self.assertEqual(Sub.meta.order_sensitive,True)
964
965        class Sub(Base1,Base2):
966            pass
967        self.assertEqual(Sub.meta.order_sensitive,True)
968
969        class Sub(Base2,Base1):
970            pass
971        self.assertEqual(Sub.meta.order_sensitive,False)
972
973
974    def test_mixing_in_other_base_classes(self):
975        class Thing(dexml2.Model):
976            testit = fields.String()
977        class Mixin(object):
978            def _get_testit(self):
979                return 42
980            def _set_testit(self,value):
981                pass
982            testit = property(_get_testit,_set_testit)
983
984        class Sub(Thing,Mixin):
985            pass
986        assert issubclass(Sub,Thing)
987        assert issubclass(Sub,Mixin)
988        s = Sub.parse('<Sub testit="hello" />')
989        self.assertEqual(s.testit,"hello")
990
991        class Sub(Mixin,Thing):
992            pass
993        assert issubclass(Sub,Thing)
994        assert issubclass(Sub,Mixin)
995        s = Sub.parse('<Sub testit="hello" />')
996        self.assertEqual(s.testit,42)
997
998
999    def test_error_using_undefined_model_class(self):
1000        class Whoopsie(dexml2.Model):
1001            value = fields.Model("UndefinedModel")
1002        self.assertRaises(ValueError,Whoopsie.parse,"<Whoopsie><UndefinedModel /></Whoopsie>")
1003        self.assertRaises(ValueError,Whoopsie,value=None)
1004
1005        class Whoopsie(dexml2.Model):
1006            value = fields.Model((None,"UndefinedModel"))
1007        self.assertRaises(ValueError,Whoopsie.parse,"<Whoopsie><UndefinedModel /></Whoopsie>")
1008        self.assertRaises(ValueError,Whoopsie,value=None)
1009
1010        class Whoopsie(dexml2.Model):
1011            value = fields.Model(("W:","UndefinedModel"))
1012        self.assertRaises(ValueError,Whoopsie.parse,"<Whoopsie><UndefinedModel /></Whoopsie>")
1013        self.assertRaises(ValueError,Whoopsie,value=None)
1014
1015
1016    def test_unordered_parse_of_list_field(self):
1017        class Notebook(dexml2.Model):
1018            class meta:
1019                order_sensitive = False
1020            notes = fields.List(fields.String(tagname="note"),tagname="notes")
1021
1022        n = Notebook.parse("<Notebook><notes><note>one</note><note>two</note></notes></Notebook>")
1023        self.assertEqual(n.notes,["one","two"])
1024
1025        Notebook.parse("<Notebook><wtf /><notes><note>one</note><note>two</note><wtf /></notes></Notebook>")
1026
1027        Notebook.meta.ignore_unknown_elements = False
1028        self.assertRaises(dexml2.ParseError, Notebook.parse, "<Notebook><wtf /><notes><note>one</note><note>two</note><wtf /></notes></Notebook>")
1029        self.assertRaises(dexml2.ParseError, Notebook.parse, "<Notebook tag='home'><notes><note>one</note><note>two</note></notes></Notebook>")
1030
1031
1032class TestListField(unittest.TestCase):
1033    class F(dexml2.Model):
1034        class meta:
1035            tagname = "f"
1036        name = fields.String(tagname="name")
1037
1038    def test_empty(self):
1039        class obj(dexml2.Model):
1040            fs = fields.List(fields.Model(self.F))
1041
1042        o = obj()
1043        self.assertEqual(o.render(fragment=True), "<obj />")
1044
1045    def test_simple(self):
1046        class obj(dexml2.Model):
1047            fs = fields.List(fields.Model(self.F))
1048
1049        o = obj()
1050        o.fs.append(self.F(name="N1"))
1051        self.assertEqual(o.render(fragment=True), "<obj><f><name>N1</name></f></obj>")
1052
1053    def test_empty_with_tagname(self):
1054        class obj(dexml2.Model):
1055            fs = fields.List(fields.Model(self.F), tagname="L")
1056
1057        o = obj()
1058        self.assertEqual(o.render(fragment=True), "<obj><L /></obj>")
1059
1060    def test_tagname(self):
1061        class obj(dexml2.Model):
1062            fs = fields.List(fields.Model(self.F), tagname="L")
1063
1064        o = obj()
1065        o.fs.append(self.F(name="N1"))
1066        self.assertEqual(o.render(fragment=True), "<obj><L><f><name>N1</name></f></L></obj>")
1067
1068    def test_model_tagname(self):
1069        class FF(self.F):
1070            pass
1071        class obj(dexml2.Model):
1072            fs = fields.List(fields.Model(FF))
1073
1074        o = obj()
1075        o.fs.append(FF(name="N1"))
1076        self.assertEqual(o.render(fragment=True), "<obj><FF><name>N1</name></FF></obj>")
1077
1078    def test_list_and_model_tagnames(self):
1079        class FF(self.F):
1080            class meta:
1081                tagname = "FF"
1082        class obj(dexml2.Model):
1083            fs = fields.List(fields.Model(FF), tagname="L")
1084
1085        o = obj()
1086        o.fs.append(FF(name="N1"))
1087        self.assertEqual(o.render(fragment=True), "<obj><L><FF><name>N1</name></FF></L></obj>")
1088
1089    def test_strings(self):
1090        class obj(dexml2.Model):
1091            fs = fields.List(fields.String(tagname="val"))
1092
1093        o = obj()
1094        o.fs.append("s1")
1095        self.assertEqual(o.render(fragment=True), "<obj><val>s1</val></obj>")
1096
1097    def test_model_field_tagname(self):
1098        class FF(self.F):
1099            pass
1100        class obj(dexml2.Model):
1101            fs = fields.Model(FF, tagname="subFF")
1102
1103        o = obj()
1104        o.fs = FF(name="N1")
1105        self.assertEqual(o.render(fragment=True), "<obj><subFF><name>N1</name></subFF></obj>")
1106
1107