diff -Nru python-xmltodict-0.12.0/CHANGELOG.md python-xmltodict-0.13.0/CHANGELOG.md --- python-xmltodict-0.12.0/CHANGELOG.md 2019-02-11 06:58:47.000000000 +0000 +++ python-xmltodict-0.13.0/CHANGELOG.md 2022-05-08 06:49:17.000000000 +0000 @@ -1,6 +1,32 @@ CHANGELOG ========= +v0.13.0 +------- + +* Add install info to readme for openSUSE. (#205) + * Thanks, @smarlowucf! +* Support defaultdict for namespace mapping (#211) + * Thanks, @nathanalderson! +* parse(generator) is now possible (#212) + * Thanks, @xandey! +* Processing comments on parsing from xml to dict (connected to #109) (#221) + * Thanks, @svetazol! +* Add expand_iter kw to unparse to expand iterables (#213) + * Thanks, @claweyenuk! +* Fixed some typos + * Thanks, @timgates42 and @kianmeng! +* Add support for python3.8 + * Thanks, @t0b3! +* Drop Jython/Python 2 and add Python 3.9/3.10. +* Drop OrderedDict in Python >= 3.7 +* Do not use len() to determine if a sequence is empty + * Thanks, @DimitriPapadopoulos! +* Add more namespace attribute tests + * Thanks, @leogregianin! +* Fix encoding issue in setup.py + * Thanks, @rjarry! + v0.12.0 ------- @@ -199,7 +225,7 @@ ------ * Merge pull request #11 from ralphbean/master -* Include REAMDE, LICENSE, and tests in the distributed tarball. +* Include README, LICENSE, and tests in the distributed tarball. v0.4.1 ------ diff -Nru python-xmltodict-0.12.0/debian/changelog python-xmltodict-0.13.0/debian/changelog --- python-xmltodict-0.12.0/debian/changelog 2020-04-01 21:48:59.000000000 +0000 +++ python-xmltodict-0.13.0/debian/changelog 2022-08-29 21:16:25.000000000 +0000 @@ -1,3 +1,16 @@ +python-xmltodict (0.13.0-1) unstable; urgency=medium + + * New upstream version 0.13.0 + * d/copyright: Update dates + * d/control: + + Bump Standards-Version to 4.6.1 (no changes needed) + + Bump debhelper version + + Use python3-nose2 (Closes: #1018595) + * d/upstream: Fix lintian upstream-metadata-missing-bug-tracking + * d/watch: Bump to version 4 + + -- Sebastien Badia Mon, 29 Aug 2022 23:16:25 +0200 + python-xmltodict (0.12.0-2) unstable; urgency=medium * Drop python2 support; Closes: #938278 diff -Nru python-xmltodict-0.12.0/debian/control python-xmltodict-0.13.0/debian/control --- python-xmltodict-0.12.0/debian/control 2020-04-01 21:48:59.000000000 +0000 +++ python-xmltodict-0.13.0/debian/control 2022-08-29 21:16:25.000000000 +0000 @@ -2,13 +2,12 @@ Section: python Priority: optional Maintainer: Sebastien Badia -Build-Depends: debhelper-compat (= 12), +Build-Depends: debhelper-compat (= 13), dh-python, python3-all, python3-setuptools -Build-Depends-Indep: python3-coverage, - python3-nose -Standards-Version: 4.4.1 +Build-Depends-Indep: python3-coverage, python3-nose2 +Standards-Version: 4.6.1 Rules-Requires-Root: no Vcs-Browser: https://salsa.debian.org/debian/python-xmltodict Vcs-Git: https://salsa.debian.org/debian/python-xmltodict.git diff -Nru python-xmltodict-0.12.0/debian/copyright python-xmltodict-0.13.0/debian/copyright --- python-xmltodict-0.12.0/debian/copyright 2020-04-01 21:48:59.000000000 +0000 +++ python-xmltodict-0.13.0/debian/copyright 2022-08-29 21:16:25.000000000 +0000 @@ -3,11 +3,11 @@ Source: https://github.com/martinblech/xmltodict Files: * -Copyright: 2012-2018 Martin Blech +Copyright: 2012-2022 Martin Blech License: MIT Files: debian/* -Copyright: 2014-2018 Sebastien Badia +Copyright: 2014-2022 Sebastien Badia License: MIT Comment: the Debian packaging is licensed under the same terms as the original package. diff -Nru python-xmltodict-0.12.0/debian/rules python-xmltodict-0.13.0/debian/rules --- python-xmltodict-0.12.0/debian/rules 2020-04-01 21:48:59.000000000 +0000 +++ python-xmltodict-0.13.0/debian/rules 2022-08-29 21:16:25.000000000 +0000 @@ -5,13 +5,6 @@ %: dh $@ --buildsystem=pybuild --with python3 - -override_dh_auto_test: - PYBUILD_SYSTEM=custom \ - PYBUILD_TEST_ARGS="nosetests3 --with-coverage --cover-package=${PYBUILD_NAME}" \ - dh_auto_test - - override_dh_auto_clean: dh_auto_clean [ ! -f .coverage ] || rm .coverage diff -Nru python-xmltodict-0.12.0/debian/salsa-ci.yml python-xmltodict-0.13.0/debian/salsa-ci.yml --- python-xmltodict-0.12.0/debian/salsa-ci.yml 1970-01-01 00:00:00.000000000 +0000 +++ python-xmltodict-0.13.0/debian/salsa-ci.yml 2022-08-29 21:16:25.000000000 +0000 @@ -0,0 +1,4 @@ +--- +include: + - https://salsa.debian.org/salsa-ci-team/pipeline/raw/master/salsa-ci.yml + - https://salsa.debian.org/salsa-ci-team/pipeline/raw/master/pipeline-jobs.yml diff -Nru python-xmltodict-0.12.0/debian/upstream/metadata python-xmltodict-0.13.0/debian/upstream/metadata --- python-xmltodict-0.12.0/debian/upstream/metadata 2020-04-01 21:48:59.000000000 +0000 +++ python-xmltodict-0.13.0/debian/upstream/metadata 2022-08-29 21:16:25.000000000 +0000 @@ -1,4 +1,5 @@ --- -Name: xmltodict +Bug-Database: https://github.com/martinblech/xmltodict/issues +Bug-Submit: https://github.com/martinblech/xmltodict/issues/new Repository: https://github.com/martinblech/xmltodict.git Repository-Browse: https://github.com/martinblech/xmltodict diff -Nru python-xmltodict-0.12.0/debian/watch python-xmltodict-0.13.0/debian/watch --- python-xmltodict-0.12.0/debian/watch 2020-04-01 21:48:59.000000000 +0000 +++ python-xmltodict-0.13.0/debian/watch 2022-08-29 21:16:25.000000000 +0000 @@ -1,3 +1,3 @@ -version=3 +version=4 opts=filenamemangle=s/.+\/v?(\d\S*)\.tar\.gz/xmltodict-$1\.tar\.gz/ \ https://github.com/martinblech/xmltodict/tags .*/v?(\d\S*)\.tar\.gz diff -Nru python-xmltodict-0.12.0/README.md python-xmltodict-0.13.0/README.md --- python-xmltodict-0.12.0/README.md 2019-02-11 06:58:47.000000000 +0000 +++ python-xmltodict-0.13.0/README.md 2022-05-08 06:49:17.000000000 +0000 @@ -2,7 +2,7 @@ `xmltodict` is a Python module that makes working with XML feel like you are working with [JSON](http://docs.python.org/library/json.html), as in this ["spec"](http://www.xml.com/pub/a/2006/05/31/converting-between-xml-and-json.html): -[![Build Status](https://secure.travis-ci.org/martinblech/xmltodict.svg)](http://travis-ci.org/martinblech/xmltodict) +[![Build Status](https://travis-ci.com/martinblech/xmltodict.svg?branch=master)](https://travis-ci.com/martinblech/xmltodict) ```python >>> print(json.dumps(xmltodict.parse(""" @@ -163,6 +163,37 @@ This is a test ``` +Lists that are specified under a key in a dictionary use the key as a tag for each item. But if a list does have a parent key, for example if a list exists inside another list, it does not have a tag to use and the items are converted to a string as shown in the example below. To give tags to nested lists, use the `expand_iter` keyword argument to provide a tag as demonstrated below. Note that using `expand_iter` will break roundtripping. + +```python +>>> mydict = { +... "line": { +... "points": [ +... [1, 5], +... [2, 6], +... ] +... } +... } +>>> print(xmltodict.unparse(mydict, pretty=True)) + + + [1, 5] + [2, 6] + +>>> print(xmltodict.unparse(mydict, pretty=True, expand_iter="coord")) + + + + 1 + 5 + + + 2 + 6 + + +``` + ## Ok, how do I get it? ### Using pypi @@ -204,3 +235,15 @@ ```sh $ pkg install py36-xmltodict ``` + +### openSUSE/SLE (SLE 15, Leap 15, Tumbleweed) + +There is an [official openSUSE package for xmltodict](https://software.opensuse.org/package/python-xmltodict). + +```sh +# Python2 +$ zypper in python2-xmltodict + +# Python3 +$ zypper in python3-xmltodict +``` diff -Nru python-xmltodict-0.12.0/setup.py python-xmltodict-0.13.0/setup.py --- python-xmltodict-0.12.0/setup.py 2019-02-11 06:58:47.000000000 +0000 +++ python-xmltodict-0.13.0/setup.py 2022-05-08 06:49:17.000000000 +0000 @@ -8,8 +8,8 @@ import xmltodict -with open('README.md') as f: - long_description = f.read() +with open('README.md', 'rb') as f: + long_description = f.read().decode('utf-8') setup(name='xmltodict', @@ -22,23 +22,23 @@ url='https://github.com/martinblech/xmltodict', license=xmltodict.__license__, platforms=['all'], - python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', + python_requires='>=3.4', classifiers=[ 'Intended Audience :: Developers', 'License :: OSI Approved :: MIT License', 'Operating System :: OS Independent', 'Programming Language :: Python', - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: Implementation :: Jython', + 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: Implementation :: PyPy', 'Topic :: Text Processing :: Markup :: XML', ], py_modules=['xmltodict'], - tests_require=['nose>=1.0', 'coverage'], + tests_require=['nose2', 'coverage'], ) diff -Nru python-xmltodict-0.12.0/tests/test_dicttoxml.py python-xmltodict-0.13.0/tests/test_dicttoxml.py --- python-xmltodict-0.12.0/tests/test_dicttoxml.py 2019-02-11 06:58:47.000000000 +0000 +++ python-xmltodict-0.13.0/tests/test_dicttoxml.py 2022-05-08 06:49:17.000000000 +0000 @@ -46,6 +46,14 @@ 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']}} diff -Nru python-xmltodict-0.12.0/tests/test_xmltodict.py python-xmltodict-0.13.0/tests/test_xmltodict.py --- python-xmltodict-0.12.0/tests/test_xmltodict.py 2019-02-11 06:58:47.000000000 +0000 +++ python-xmltodict-0.13.0/tests/test_xmltodict.py 2022-05-08 06:49:17.000000000 +0000 @@ -1,4 +1,5 @@ from xmltodict import parse, ParsingInterrupted +import collections import unittest try: @@ -119,6 +120,17 @@ parse, 'x', item_depth=1, item_callback=cb) + def test_streaming_generator(self): + def cb(path, item): + cb.count += 1 + self.assertEqual(path, [('a', {'x': 'y'}), ('b', None)]) + self.assertEqual(item, str(cb.count)) + return True + cb.count = 0 + parse((n for n in '123'), + item_depth=2, item_callback=cb) + self.assertEqual(cb.count, 3) + def test_postprocessor(self): def postprocessor(path, key, value): try: @@ -171,7 +183,8 @@ xml = """ + xmlns:b="http://b.com/" + version="1.00"> 1 2 3 @@ -179,12 +192,13 @@ """ d = { 'http://defaultns.com/:root': { + '@version': '1.00', + '@xmlns': { + '': 'http://defaultns.com/', + 'a': 'http://a.com/', + 'b': 'http://b.com/', + }, 'http://defaultns.com/:x': { - '@xmlns': { - '': 'http://defaultns.com/', - 'a': 'http://a.com/', - 'b': 'http://b.com/', - }, '@http://a.com/:attr': 'val', '#text': '1', }, @@ -199,7 +213,8 @@ xml = """ + xmlns:b="http://b.com/" + version="1.00"> 1 2 3 @@ -211,12 +226,13 @@ } d = { 'root': { + '@version': '1.00', + '@xmlns': { + '': 'http://defaultns.com/', + 'a': 'http://a.com/', + 'b': 'http://b.com/', + }, 'x': { - '@xmlns': { - '': 'http://defaultns.com/', - 'a': 'http://a.com/', - 'b': 'http://b.com/', - }, '@ns_a:attr': 'val', '#text': '1', }, @@ -227,11 +243,43 @@ res = parse(xml, process_namespaces=True, namespaces=namespaces) self.assertEqual(res, d) + def test_namespace_collapse_all(self): + xml = """ + + 1 + 2 + 3 + + """ + namespaces = collections.defaultdict(lambda: None) + d = { + 'root': { + '@version': '1.00', + '@xmlns': { + '': 'http://defaultns.com/', + 'a': 'http://a.com/', + 'b': 'http://b.com/', + }, + 'x': { + '@attr': 'val', + '#text': '1', + }, + 'y': '2', + 'z': '3', + }, + } + res = parse(xml, process_namespaces=True, namespaces=namespaces) + self.assertEqual(res, d) + def test_namespace_ignore(self): xml = """ + xmlns:b="http://b.com/" + version="1.00"> 1 2 3 @@ -242,6 +290,7 @@ '@xmlns': 'http://defaultns.com/', '@xmlns:a': 'http://a.com/', '@xmlns:b': 'http://b.com/', + '@version': '1.00', 'x': '1', 'a:y': '2', 'b:z': '3', @@ -380,3 +429,31 @@ else: self.assertTrue(False) expat.ParserCreate = ParserCreate + + def test_comments(self): + xml = """ + + + + + + 1 + + 2 + + + """ + expectedResult = { + 'a': { + 'b': { + '#comment': 'b comment', + 'c': { + + '#comment': 'c comment', + '#text': '1', + }, + 'd': '2', + }, + } + } + self.assertEqual(parse(xml, process_comments=True), expectedResult) diff -Nru python-xmltodict-0.12.0/tox.ini python-xmltodict-0.13.0/tox.ini --- python-xmltodict-0.12.0/tox.ini 2019-02-11 06:58:47.000000000 +0000 +++ python-xmltodict-0.13.0/tox.ini 2022-05-08 06:49:17.000000000 +0000 @@ -1,7 +1,7 @@ [tox] -envlist = py27, py34, py35, py36, py37, pypy +envlist = py34, py35, py36, py37, py38, py39, py310, pypy [testenv] deps = coverage - nose -commands=nosetests --with-coverage --cover-package=xmltodict + nose2 +commands=nose2 --coverage=xmltodict.py diff -Nru python-xmltodict-0.12.0/.travis.yml python-xmltodict-0.13.0/.travis.yml --- python-xmltodict-0.12.0/.travis.yml 2019-02-11 06:58:47.000000000 +0000 +++ python-xmltodict-0.13.0/.travis.yml 2022-05-08 06:49:17.000000000 +0000 @@ -1,26 +1,15 @@ language: python python: - - "2.7" - "3.4" - "3.5" - "3.6" + - "3.7" + - "3.8" + - "3.9" + - "3.10-dev" - "pypy" -matrix: - include: - - python: "3.7" - dist: xenial - sudo: required - - name: "Jython" - python: "pypy" - env: JYTHON_VERSION="2.7.0" - before_install: - - export JYTHON_URL="http://search.maven.org/remotecontent?filepath=org/python/jython-installer/${JYTHON_VERSION}/jython-installer-${JYTHON_VERSION}.jar" - - wget $JYTHON_URL -O jython_installer.jar - - java -jar jython_installer.jar -s -d $HOME/jython - - export PATH="$HOME/jython:$PATH" - - $HOME/jython/bin/easy_install nose - script: $HOME/jython/bin/nosetests +install: pip install nose2 -script: nosetests --with-coverage --cover-package=xmltodict +script: nose2 -vv --coverage=xmltodict.py diff -Nru python-xmltodict-0.12.0/xmltodict.py python-xmltodict-0.13.0/xmltodict.py --- python-xmltodict-0.12.0/xmltodict.py 2019-02-11 06:58:47.000000000 +0000 +++ python-xmltodict-0.13.0/xmltodict.py 2022-05-08 06:49:17.000000000 +0000 @@ -15,7 +15,12 @@ except ImportError: from io import StringIO -from collections import OrderedDict +_dict = dict +import platform +if tuple(map(int, platform.python_version_tuple()[:2])) < (3, 7): + from collections import OrderedDict as _dict + +from inspect import isgenerator try: # pragma no cover _basestring = basestring @@ -27,7 +32,7 @@ _unicode = str __author__ = 'Martin Blech' -__version__ = '0.12.0' +__version__ = '0.13.0' __license__ = 'MIT' @@ -45,11 +50,12 @@ force_cdata=False, cdata_separator='', postprocessor=None, - dict_constructor=OrderedDict, + dict_constructor=_dict, strip_whitespace=True, namespace_separator=':', namespaces=None, - force_list=None): + force_list=None, + comment_key='#comment'): self.path = [] self.stack = [] self.data = [] @@ -66,17 +72,21 @@ self.strip_whitespace = strip_whitespace self.namespace_separator = namespace_separator self.namespaces = namespaces - self.namespace_declarations = OrderedDict() + self.namespace_declarations = dict_constructor() self.force_list = force_list + self.comment_key = comment_key def _build_name(self, full_name): - if not self.namespaces: + if self.namespaces is None: return full_name i = full_name.rfind(self.namespace_separator) if i == -1: return full_name namespace, name = full_name[:i], full_name[i+1:] - short_namespace = self.namespaces.get(namespace, namespace) + try: + short_namespace = self.namespaces[namespace] + except KeyError: + short_namespace = namespace if not short_namespace: return name else: @@ -95,7 +105,7 @@ attrs = self._attrs_to_dict(attrs) if attrs and self.namespace_declarations: attrs['xmlns'] = self.namespace_declarations - self.namespace_declarations = OrderedDict() + self.namespace_declarations = self.dict_constructor() self.path.append((name, attrs or None)) if len(self.path) > self.item_depth: self.stack.append((self.item, self.data)) @@ -126,7 +136,7 @@ should_continue = self.item_callback(self.path, item) if not should_continue: raise ParsingInterrupted() - if len(self.stack): + if self.stack: data = (None if not self.data else self.cdata_separator.join(self.data)) item = self.item @@ -152,6 +162,11 @@ else: self.data.append(data) + def comments(self, data): + if self.strip_whitespace: + data = data.strip() + self.item = self.push_data(self.item, self.comment_key, data) + def push_data(self, item, key, data): if self.postprocessor is not None: result = self.postprocessor(self.path, key, data) @@ -185,10 +200,10 @@ def parse(xml_input, encoding=None, expat=expat, process_namespaces=False, - namespace_separator=':', disable_entities=True, **kwargs): + namespace_separator=':', disable_entities=True, process_comments=False, **kwargs): """Parse the given XML input and convert it into a dictionary. - `xml_input` can either be a `string` or a file-like object. + `xml_input` can either be a `string`, a file-like object, or a generator of strings. If `xml_attribs` is `True`, element attributes are put in the dictionary among regular child elements, using `@` as a prefix to avoid collisions. If @@ -243,21 +258,21 @@ ... return key, value >>> xmltodict.parse('12x', ... postprocessor=postprocessor) - OrderedDict([(u'a', OrderedDict([(u'b:int', [1, 2]), (u'b', u'x')]))]) + {'a': {'b:int': [1, 2], 'b': 'x'}} You can pass an alternate version of `expat` (such as `defusedexpat`) by using the `expat` parameter. E.g: >>> import defusedexpat >>> xmltodict.parse('hello', expat=defusedexpat.pyexpat) - OrderedDict([(u'a', u'hello')]) + {'a': 'hello'} You can use the force_list argument to force lists to be created even when there is only a single child of a given level of hierarchy. The force_list argument is a tuple of keys. If the key for a given level of hierarchy is in the force_list argument, that level of hierarchy will have a list as a child (even if there is only one sub-element). - The index_keys operation takes precendence over this. This is applied + The index_keys operation takes precedence over this. This is applied after any user-supplied postprocessor has already run. For example, given this input: @@ -287,6 +302,36 @@ `force_list` can also be a callable that receives `path`, `key` and `value`. This is helpful in cases where the logic that decides whether a list should be forced is more complex. + + + If `process_comment` is `True` then comment will be added with comment_key + (default=`'#comment'`) to then tag which contains comment + + For example, given this input: + + + + + + 1 + + 2 + + + + If called with process_comment=True, it will produce + this dictionary: + 'a': { + 'b': { + '#comment': 'b comment', + 'c': { + + '#comment': 'c comment', + '#text': '1', + }, + 'd': '2', + }, + } """ handler = _DictSAXHandler(namespace_separator=namespace_separator, **kwargs) @@ -309,6 +354,8 @@ parser.StartElementHandler = handler.startElement parser.EndElementHandler = handler.endElement parser.CharacterDataHandler = handler.characters + if process_comments: + parser.CommentHandler = handler.comments parser.buffer_text = True if disable_entities: try: @@ -323,6 +370,10 @@ parser.ExternalEntityRefHandler = lambda *x: 1 if hasattr(xml_input, 'read'): parser.ParseFile(xml_input) + elif isgenerator(xml_input): + for chunk in xml_input: + parser.Parse(chunk,False) + parser.Parse(b'',True) else: parser.Parse(xml_input, True) return handler.item @@ -353,7 +404,8 @@ indent='\t', namespace_separator=':', namespaces=None, - full_document=True): + full_document=True, + expand_iter=None): key = _process_namespace(key, namespaces, namespace_separator, attr_prefix) if preprocessor is not None: result = preprocessor(key, value) @@ -368,18 +420,21 @@ if full_document and depth == 0 and index > 0: raise ValueError('document with multiple roots') if v is None: - v = OrderedDict() + v = _dict() elif isinstance(v, bool): if v: v = _unicode('true') else: v = _unicode('false') elif not isinstance(v, dict): - v = _unicode(v) + if expand_iter and hasattr(v, '__iter__') and not isinstance(v, _basestring): + v = _dict(((expand_iter, v),)) + else: + v = _unicode(v) if isinstance(v, _basestring): - v = OrderedDict(((cdata_key, v),)) + v = _dict(((cdata_key, v),)) cdata = None - attrs = OrderedDict() + attrs = _dict() children = [] for ik, iv in v.items(): if ik == cdata_key: @@ -407,7 +462,8 @@ _emit(child_key, child_value, content_handler, attr_prefix, cdata_key, depth+1, preprocessor, pretty, newl, indent, namespaces=namespaces, - namespace_separator=namespace_separator) + namespace_separator=namespace_separator, + expand_iter=expand_iter) if cdata is not None: content_handler.characters(cdata) if pretty and children: