diff -Nru python-xmltodict-0.10.2/CHANGELOG.md python-xmltodict-0.11.0/CHANGELOG.md --- python-xmltodict-0.10.2/CHANGELOG.md 2016-06-02 05:39:32.000000000 +0000 +++ python-xmltodict-0.11.0/CHANGELOG.md 2017-04-27 18:55:43.000000000 +0000 @@ -1,6 +1,22 @@ CHANGELOG ========= +v0.11.0 +------- + +* Determine fileness by checking for `read` attr + * Thanks, @jwodder! +* Add support for Python 3.6. + * Thanks, @cclauss! +* Release as a universal wheel. + * Thanks, @adamchainz! +* Updated docs examples to use print function. + * Thanks, @cdeil! +* unparse: pass short_empty_elements to XMLGenerator + * Thanks, @zhanglei002! +* Added namespace support when unparsing. + * Thanks, @imiric! + v0.10.2 ------- diff -Nru python-xmltodict-0.10.2/debian/changelog python-xmltodict-0.11.0/debian/changelog --- python-xmltodict-0.10.2/debian/changelog 2016-04-06 12:40:46.000000000 +0000 +++ python-xmltodict-0.11.0/debian/changelog 2017-05-13 15:40:12.000000000 +0000 @@ -1,3 +1,13 @@ +python-xmltodict (0.11.0-1) unstable; urgency=medium + + * d/control: Move testing packages to Build-Depends-Indep. + Thanks zigo! + * New upstream version 0.11.0 + * d/compat: Switch to compat-version 10 + * d/control: Use my Debian email address + + -- Sebastien Badia Sat, 13 May 2017 17:40:12 +0200 + python-xmltodict (0.10.2-1) unstable; urgency=medium * New upstream release. @@ -6,7 +16,7 @@ Update Vcs-* fields (switch to https). Remove python-versions. - -- Sebastien Badia Wed, 06 Apr 2016 14:40:46 +0200 + -- Sebastien Badia Mon, 19 Sep 2016 16:30:34 +0200 python-xmltodict (0.9.2-3) unstable; urgency=medium diff -Nru python-xmltodict-0.10.2/debian/compat python-xmltodict-0.11.0/debian/compat --- python-xmltodict-0.10.2/debian/compat 2016-04-06 12:40:46.000000000 +0000 +++ python-xmltodict-0.11.0/debian/compat 2017-05-13 15:40:12.000000000 +0000 @@ -1 +1 @@ -9 +10 diff -Nru python-xmltodict-0.10.2/debian/control python-xmltodict-0.11.0/debian/control --- python-xmltodict-0.10.2/debian/control 2016-04-06 12:40:46.000000000 +0000 +++ python-xmltodict-0.11.0/debian/control 2017-05-13 15:40:12.000000000 +0000 @@ -1,17 +1,17 @@ Source: python-xmltodict Section: python Priority: optional -Maintainer: Sebastien Badia -Build-Depends: debhelper (>= 9), +Maintainer: Sebastien Badia +Build-Depends: debhelper (>= 10), dh-python, python-all, - python-coverage, - python-nose, python-setuptools, python3-all, - python3-coverage, - python3-nose, python3-setuptools +Build-Depends-Indep: python-coverage, + python-nose, + python3-coverage, + python3-nose Standards-Version: 3.9.8 Vcs-Browser: https://anonscm.debian.org/cgit/collab-maint/python-xmltodict.git Vcs-Git: https://anonscm.debian.org/git/collab-maint/python-xmltodict.git diff -Nru python-xmltodict-0.10.2/debian/gbp.conf python-xmltodict-0.11.0/debian/gbp.conf --- python-xmltodict-0.10.2/debian/gbp.conf 2016-04-06 12:40:46.000000000 +0000 +++ python-xmltodict-0.11.0/debian/gbp.conf 2017-05-13 15:40:12.000000000 +0000 @@ -3,5 +3,5 @@ debian-branch = master pristine-tar = True -[git-buildpackage] +[buildpackage] export-dir = ../build-area/ diff -Nru python-xmltodict-0.10.2/push_release.sh python-xmltodict-0.11.0/push_release.sh --- python-xmltodict-0.10.2/push_release.sh 1970-01-01 00:00:00.000000000 +0000 +++ python-xmltodict-0.11.0/push_release.sh 2017-04-27 18:55:43.000000000 +0000 @@ -0,0 +1,2 @@ +#!/usr/bin/env sh +python setup.py clean sdist bdist_wheel upload diff -Nru python-xmltodict-0.10.2/README.md python-xmltodict-0.11.0/README.md --- python-xmltodict-0.10.2/README.md 2016-06-02 05:39:32.000000000 +0000 +++ python-xmltodict-0.11.0/README.md 2017-04-27 18:55:43.000000000 +0000 @@ -80,7 +80,7 @@ ```python >>> def handle_artist(_, artist): -... print artist['name'] +... print(artist['name']) ... return True >>> >>> xmltodict.parse(GzipFile('discogs_artists.xml.gz'), @@ -98,7 +98,7 @@ import sys, marshal while True: _, article = marshal.load(sys.stdin) - print article['title'] + print(article['title']) ``` ```sh @@ -138,7 +138,7 @@ ... 'last_updated': '2014-02-16T23:10:12Z', ... } ... } ->>> print unparse(mydict, pretty=True) +>>> print(unparse(mydict, pretty=True)) good @@ -158,7 +158,7 @@ ... '#text':'This is a test' ... } ... } ->>> print xmltodict.unparse(mydict, pretty=True) +>>> print(xmltodict.unparse(mydict, pretty=True)) This is a test ``` diff -Nru python-xmltodict-0.10.2/setup.cfg python-xmltodict-0.11.0/setup.cfg --- python-xmltodict-0.10.2/setup.cfg 1970-01-01 00:00:00.000000000 +0000 +++ python-xmltodict-0.11.0/setup.cfg 2017-04-27 18:55:43.000000000 +0000 @@ -0,0 +1,2 @@ +[bdist_wheel] +universal = 1 diff -Nru python-xmltodict-0.10.2/setup.py python-xmltodict-0.11.0/setup.py --- python-xmltodict-0.10.2/setup.py 2016-06-02 05:39:32.000000000 +0000 +++ python-xmltodict-0.11.0/setup.py 2017-04-27 18:55:43.000000000 +0000 @@ -30,6 +30,7 @@ 'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: Implementation :: Jython', 'Programming Language :: Python :: Implementation :: PyPy', 'Topic :: Text Processing :: Markup :: XML', diff -Nru python-xmltodict-0.10.2/tests/test_dicttoxml.py python-xmltodict-0.11.0/tests/test_dicttoxml.py --- python-xmltodict-0.10.2/tests/test_dicttoxml.py 2016-06-02 05:39:32.000000000 +0000 +++ python-xmltodict-0.11.0/tests/test_dicttoxml.py 2017-04-27 18:55:43.000000000 +0000 @@ -163,3 +163,39 @@ 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 < (3, 2): + 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) diff -Nru python-xmltodict-0.10.2/tests/test_xmltodict.py python-xmltodict-0.11.0/tests/test_xmltodict.py --- python-xmltodict-0.10.2/tests/test_xmltodict.py 2016-06-02 05:39:32.000000000 +0000 +++ python-xmltodict-0.11.0/tests/test_xmltodict.py 2017-04-27 18:55:43.000000000 +0000 @@ -9,6 +9,8 @@ except ImportError: from xmltodict import StringIO +from xml.parsers.expat import ParserCreate +from xml.parsers import expat def _encode(s): try: @@ -180,6 +182,11 @@ d = { 'http://defaultns.com/:root': { 'http://defaultns.com/:x': { + '@xmlns': { + '': 'http://defaultns.com/', + 'a': 'http://a.com/', + 'b': 'http://b.com/', + }, '@http://a.com/:attr': 'val', '#text': '1', }, @@ -187,7 +194,8 @@ 'http://b.com/:z': '3', } } - self.assertEqual(parse(xml, process_namespaces=True), d) + res = parse(xml, process_namespaces=True) + self.assertEqual(res, d) def test_namespace_collapse(self): xml = """ @@ -200,12 +208,17 @@ """ namespaces = { - 'http://defaultns.com/': None, + 'http://defaultns.com/': '', 'http://a.com/': 'ns_a', } d = { 'root': { 'x': { + '@xmlns': { + '': 'http://defaultns.com/', + 'a': 'http://a.com/', + 'b': 'http://b.com/', + }, '@ns_a:attr': 'val', '#text': '1', }, @@ -213,8 +226,8 @@ 'http://b.com/:z': '3', }, } - self.assertEqual( - parse(xml, process_namespaces=True, namespaces=namespaces), d) + res = parse(xml, process_namespaces=True, namespaces=namespaces) + self.assertEqual(res, d) def test_namespace_ignore(self): xml = """ @@ -295,3 +308,76 @@ }, } self.assertEqual(parse(xml, force_list=force_list, dict_constructor=dict), expectedResult) + + def test_disable_entities_true_ignores_xmlbomb(self): + xml = """ + + + + ]> + &c; + """ + expectedResult = {'bomb': None} + try: + parse_attempt = parse(xml, disable_entities=True) + except expat.ExpatError: + self.assertTrue(True) + else: + self.assertEqual(parse_attempt, expectedResult) + + def test_disable_entities_false_returns_xmlbomb(self): + xml = """ + + + + ]> + &c; + """ + bomb = "1234567890" * 64 + expectedResult = {'bomb': bomb} + self.assertEqual(parse(xml, disable_entities=False), expectedResult) + + def test_disable_entities_true_ignores_external_dtd(self): + xml = """ + + ]> + + """ + expectedResult = {'root': None} + try: + parse_attempt = parse(xml, disable_entities=True) + except expat.ExpatError: + self.assertTrue(True) + else: + self.assertEqual(parse_attempt, expectedResult) + + def test_disable_entities_true_attempts_external_dtd(self): + xml = """ + + ]> + + """ + def raising_external_ref_handler(*args, **kwargs): + parser = ParserCreate(*args, **kwargs) + parser.ExternalEntityRefHandler = lambda *x: 0 + try: + feature = "http://apache.org/xml/features/disallow-doctype-decl" + parser._reader.setFeature(feature, True) + except AttributeError: + pass + return parser + expat.ParserCreate = raising_external_ref_handler + # Using this try/catch because a TypeError is thrown before + # the ExpatError, and Python 2.6 is confused by that. + try: + parse(xml, disable_entities=False, expat=expat) + except expat.ExpatError: + self.assertTrue(True) + else: + self.assertTrue(False) + expat.ParserCreate = ParserCreate + diff -Nru python-xmltodict-0.10.2/tox.ini python-xmltodict-0.11.0/tox.ini --- python-xmltodict-0.10.2/tox.ini 2016-06-02 05:39:32.000000000 +0000 +++ python-xmltodict-0.11.0/tox.ini 2017-04-27 18:55:43.000000000 +0000 @@ -1,5 +1,5 @@ [tox] -envlist = py27,py34,py35,pypy,py26 +envlist = py27,py34,py35,py36,pypy,py26 [testenv] deps = coverage diff -Nru python-xmltodict-0.10.2/.travis.yml python-xmltodict-0.11.0/.travis.yml --- python-xmltodict-0.10.2/.travis.yml 2016-06-02 05:39:32.000000000 +0000 +++ python-xmltodict-0.11.0/.travis.yml 2017-04-27 18:55:43.000000000 +0000 @@ -7,6 +7,7 @@ - "3.3" - "3.4" - "3.5" + - "3.6" - "pypy" env: @@ -27,6 +28,8 @@ env: JYTHON=true - python: "3.5" env: JYTHON=true + - python: "3.6" + env: JYTHON=true before_install: - export JYTHON_URL='http://search.maven.org/remotecontent?filepath=org/python/jython-installer/2.7.0/jython-installer-2.7.0.jar' diff -Nru python-xmltodict-0.10.2/xmltodict.py python-xmltodict-0.11.0/xmltodict.py --- python-xmltodict-0.10.2/xmltodict.py 2016-06-02 05:39:32.000000000 +0000 +++ python-xmltodict-0.11.0/xmltodict.py 2017-04-27 18:55:43.000000000 +0000 @@ -32,7 +32,7 @@ _unicode = str __author__ = 'Martin Blech' -__version__ = '0.10.2' +__version__ = '0.11.0' __license__ = 'MIT' @@ -71,6 +71,7 @@ self.strip_whitespace = strip_whitespace self.namespace_separator = namespace_separator self.namespaces = namespaces + self.namespace_declarations = OrderedDict() self.force_list = force_list def _build_name(self, full_name): @@ -91,9 +92,15 @@ return attrs return self.dict_constructor(zip(attrs[0::2], attrs[1::2])) + def startNamespaceDecl(self, prefix, uri): + self.namespace_declarations[prefix or ''] = uri + def startElement(self, full_name, attrs): name = self._build_name(full_name) attrs = self._attrs_to_dict(attrs) + if attrs and self.namespace_declarations: + attrs['xmlns'] = self.namespace_declarations + self.namespace_declarations = OrderedDict() self.path.append((name, attrs or None)) if len(self.path) > self.item_depth: self.stack.append((self.item, self.data)) @@ -181,7 +188,7 @@ def parse(xml_input, encoding=None, expat=expat, process_namespaces=False, - namespace_separator=':', **kwargs): + namespace_separator=':', disable_entities=True, **kwargs): """Parse the given XML input and convert it into a dictionary. `xml_input` can either be a `string` or a file-like object. @@ -217,7 +224,7 @@ Streaming example:: >>> def handle(path, item): - ... print 'path:%s item:%s' % (path, item) + ... print('path:%s item:%s' % (path, item)) ... return True ... >>> xmltodict.parse(\"\"\" @@ -301,17 +308,44 @@ except AttributeError: # Jython's expat does not support ordered_attributes pass + parser.StartNamespaceDeclHandler = handler.startNamespaceDecl parser.StartElementHandler = handler.startElement parser.EndElementHandler = handler.endElement parser.CharacterDataHandler = handler.characters parser.buffer_text = True - try: + if disable_entities: + try: + # Attempt to disable DTD in Jython's expat parser (Xerces-J). + feature = "http://apache.org/xml/features/disallow-doctype-decl" + parser._reader.setFeature(feature, True) + except AttributeError: + # For CPython / expat parser. + # Anything not handled ends up here and entities aren't expanded. + parser.DefaultHandler = lambda x: None + # Expects an integer return; zero means failure -> expat.ExpatError. + parser.ExternalEntityRefHandler = lambda *x: 1 + if hasattr(xml_input, 'read'): parser.ParseFile(xml_input) - except (TypeError, AttributeError): + else: parser.Parse(xml_input, True) return handler.item +def _process_namespace(name, namespaces, ns_sep=':', attr_prefix='@'): + if not namespaces: + return name + try: + ns, name = name.rsplit(ns_sep, 1) + except ValueError: + pass + else: + ns_res = namespaces.get(ns.strip(attr_prefix)) + name = '{0}{1}{2}{3}'.format( + attr_prefix if ns.startswith(attr_prefix) else '', + ns_res, ns_sep, name) if ns_res else name + return name + + def _emit(key, value, content_handler, attr_prefix='@', cdata_key='#text', @@ -320,7 +354,10 @@ pretty=False, newl='\n', indent='\t', + namespace_separator=':', + namespaces=None, full_document=True): + key = _process_namespace(key, namespaces, namespace_separator, attr_prefix) if preprocessor is not None: result = preprocessor(key, value) if result is None: @@ -347,6 +384,13 @@ cdata = iv continue if ik.startswith(attr_prefix): + ik = _process_namespace(ik, namespaces, namespace_separator, + attr_prefix) + if ik == '@xmlns' and isinstance(iv, dict): + for k, v in iv.items(): + attr = 'xmlns{0}'.format(':{0}'.format(k) if k else '') + attrs[attr] = _unicode(v) + continue if not isinstance(iv, _unicode): iv = _unicode(iv) attrs[ik[len(attr_prefix):]] = iv @@ -360,7 +404,8 @@ for child_key, child_value in children: _emit(child_key, child_value, content_handler, attr_prefix, cdata_key, depth+1, preprocessor, - pretty, newl, indent) + pretty, newl, indent, namespaces=namespaces, + namespace_separator=namespace_separator) if cdata is not None: content_handler.characters(cdata) if pretty and children: @@ -371,6 +416,7 @@ def unparse(input_dict, output=None, encoding='utf-8', full_document=True, + short_empty_elements=False, **kwargs): """Emit an XML document for the given `input_dict` (reverse of `parse`). @@ -392,7 +438,10 @@ if output is None: output = StringIO() must_return = True - content_handler = XMLGenerator(output, encoding) + if short_empty_elements: + content_handler = XMLGenerator(output, encoding, True) + else: + content_handler = XMLGenerator(output, encoding) if full_document: content_handler.startDocument() for key, value in input_dict.items():