import sys from xmltodict import parse, unparse from collections import OrderedDict import unittest import re from textwrap import dedent IS_JYTHON = sys.platform.startswith('java') _HEADER_RE = re.compile(r'^[^\n]*\n') def _strip(fullxml): return _HEADER_RE.sub('', fullxml) class DictToXMLTestCase(unittest.TestCase): def test_root(self): obj = {'a': None} self.assertEqual(obj, parse(unparse(obj))) self.assertEqual(unparse(obj), unparse(parse(unparse(obj)))) def test_simple_cdata(self): obj = {'a': 'b'} self.assertEqual(obj, parse(unparse(obj))) self.assertEqual(unparse(obj), unparse(parse(unparse(obj)))) def test_cdata(self): obj = {'a': {'#text': 'y'}} self.assertEqual(obj, parse(unparse(obj), force_cdata=True)) self.assertEqual(unparse(obj), unparse(parse(unparse(obj)))) def test_attrib(self): obj = {'a': {'@href': 'x'}} self.assertEqual(obj, parse(unparse(obj))) self.assertEqual(unparse(obj), unparse(parse(unparse(obj)))) def test_attrib_and_cdata(self): obj = {'a': {'@href': 'x', '#text': 'y'}} self.assertEqual(obj, parse(unparse(obj))) self.assertEqual(unparse(obj), unparse(parse(unparse(obj)))) def test_list(self): obj = {'a': {'b': ['1', '2', '3']}} self.assertEqual(obj, parse(unparse(obj))) self.assertEqual(unparse(obj), unparse(parse(unparse(obj)))) def test_list_expand_iter(self): obj = {'a': {'b': [['1', '2'], ['3',]]}} #self.assertEqual(obj, parse(unparse(obj, expand_iter="item"))) exp_xml = dedent('''\ 123''') self.assertEqual(exp_xml, unparse(obj, expand_iter="item")) def test_generator(self): obj = {'a': {'b': ['1', '2', '3']}} def lazy_obj(): return {'a': {'b': (i for i in ('1', '2', '3'))}} self.assertEqual(obj, parse(unparse(lazy_obj()))) self.assertEqual(unparse(lazy_obj()), unparse(parse(unparse(lazy_obj())))) def test_no_root(self): self.assertRaises(ValueError, unparse, {}) def test_multiple_roots(self): self.assertRaises(ValueError, unparse, {'a': '1', 'b': '2'}) self.assertRaises(ValueError, unparse, {'a': ['1', '2', '3']}) def test_no_root_nofulldoc(self): self.assertEqual(unparse({}, full_document=False), '') def test_multiple_roots_nofulldoc(self): obj = OrderedDict((('a', 1), ('b', 2))) xml = unparse(obj, full_document=False) self.assertEqual(xml, '12') obj = {'a': [1, 2]} xml = unparse(obj, full_document=False) self.assertEqual(xml, '12') def test_nested(self): obj = {'a': {'b': '1', 'c': '2'}} self.assertEqual(obj, parse(unparse(obj))) self.assertEqual(unparse(obj), unparse(parse(unparse(obj)))) obj = {'a': {'b': {'c': {'@a': 'x', '#text': 'y'}}}} self.assertEqual(obj, parse(unparse(obj))) self.assertEqual(unparse(obj), unparse(parse(unparse(obj)))) def test_semistructured(self): xml = 'abcefg' self.assertEqual(_strip(unparse(parse(xml))), 'abcefg') def test_preprocessor(self): obj = {'a': OrderedDict((('b:int', [1, 2]), ('b', 'c')))} def p(key, value): try: key, _ = key.split(':') except ValueError: pass return key, value self.assertEqual(_strip(unparse(obj, preprocessor=p)), '12c') def test_preprocessor_skipkey(self): obj = {'a': {'b': 1, 'c': 2}} def p(key, value): if key == 'b': return None return key, value self.assertEqual(_strip(unparse(obj, preprocessor=p)), '2') if not IS_JYTHON: # Jython's SAX does not preserve attribute order def test_attr_order_roundtrip(self): xml = '' self.assertEqual(xml, _strip(unparse(parse(xml)))) def test_pretty_print(self): obj = {'a': OrderedDict(( ('b', [{'c': [1, 2]}, 3]), ('x', 'y'), ))} newl = '\n' indent = '....' xml = dedent('''\ .... ........1 ........2 .... ....3 ....y ''') self.assertEqual(xml, unparse(obj, pretty=True, newl=newl, indent=indent)) def test_encoding(self): try: value = unichr(39321) except NameError: value = chr(39321) obj = {'a': value} utf8doc = unparse(obj, encoding='utf-8') latin1doc = unparse(obj, encoding='iso-8859-1') self.assertEqual(parse(utf8doc), parse(latin1doc)) self.assertEqual(parse(utf8doc), obj) def test_fulldoc(self): xml_declaration_re = re.compile( '^' + re.escape('')) self.assertTrue(xml_declaration_re.match(unparse({'a': 1}))) self.assertFalse( xml_declaration_re.match(unparse({'a': 1}, full_document=False))) def test_non_string_value(self): obj = {'a': 1} self.assertEqual('1', _strip(unparse(obj))) def test_non_string_attr(self): obj = {'a': {'@attr': 1}} self.assertEqual('', _strip(unparse(obj))) def test_short_empty_elements(self): if sys.version_info[0] < 3: return obj = {'a': None} self.assertEqual('', _strip(unparse(obj, short_empty_elements=True))) def test_namespace_support(self): obj = OrderedDict(( ('http://defaultns.com/:root', OrderedDict(( ('@xmlns', OrderedDict(( ('', 'http://defaultns.com/'), ('a', 'http://a.com/'), ('b', 'http://b.com/'), ))), ('http://defaultns.com/:x', OrderedDict(( ('@http://a.com/:attr', 'val'), ('#text', '1'), ))), ('http://a.com/:y', '2'), ('http://b.com/:z', '3'), ))), )) ns = { 'http://defaultns.com/': '', 'http://a.com/': 'a', 'http://b.com/': 'b', } expected_xml = ''' 123''' xml = unparse(obj, namespaces=ns) self.assertEqual(xml, expected_xml) def test_boolean_unparse(self): expected_xml = '\ntrue' xml = unparse(dict(x=True)) self.assertEqual(xml, expected_xml) expected_xml = '\nfalse' xml = unparse(dict(x=False)) self.assertEqual(xml, expected_xml)