././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 011452 x ustar 00 0000000 0000000 28 mtime=1586988343.1184306
dict2xml-1.7.0/ 0000755 0000765 0000024 00000000000 00000000000 015101 5 ustar 00stephenmoore staff 0000000 0000000 ././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1582686701.0
dict2xml-1.7.0/LICENSE 0000644 0000765 0000024 00000002071 00000000000 016106 0 ustar 00stephenmoore staff 0000000 0000000 The MIT License (MIT)
Copyright (c) 2018 Stephen Moore
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1582686701.0
dict2xml-1.7.0/MANIFEST.in 0000644 0000765 0000024 00000000043 00000000000 016634 0 ustar 00stephenmoore staff 0000000 0000000 include README.rst
include LICENSE
././@PaxHeader 0000000 0000000 0000000 00000000033 00000000000 011451 x ustar 00 0000000 0000000 27 mtime=1586988343.118079
dict2xml-1.7.0/PKG-INFO 0000644 0000765 0000024 00000007363 00000000000 016207 0 ustar 00stephenmoore staff 0000000 0000000 Metadata-Version: 2.1
Name: dict2xml
Version: 1.7.0
Summary: Small utility to convert a python dictionary into an XML string
Home-page: http://github.com/delfick/python-dict2xml
Author: Stephen Moore
Author-email: stephen@delfick.com
License: MIT
Description: dict2xml
========
Super Simple utility to convert a python dictionary into an xml string
Installation
------------
Make youself a virtualenv and do the following::
$ pip install .
Or if you want to develop on dict2xml, recommended commands are::
$ pip install -e .
$ pip install -e ".[tests]"
Or if you don't want to install from source::
$ pip install dict2xml
example
-------
.. code-block:: python
from dict2xml import dict2xml
data = {
'a': 1,
'b': [2, 3],
'c': {
'd': [
{'p': 9},
{'o': 10}
],
'e': 7
}
}
print dict2xml(data, wrap="all", indent=" ")
Output
------
.. code-block:: xml
1
2
3
9
10
7
methods
-------
``dict2xml.dict2xml(data, *args, **kwargs)``
Equivalent to:
.. code-block:: python
dict2xml.Converter(*args, **kwargs).build(data)
``dict2xml.Converter(wrap="", indent=" ", newlines=True)``
Knows how to convert a dictionary into an xml string
* wrap: Wraps the entire tree in this tag
* indent: Amount to prefix each line for each level of nesting
* newlines: Whether or not to use newlines
``dict2xml.Converter.build(data)``
Instance method on Converter that takes in the data and creates the xml string
Limitations
-----------
* No attributes on elements
* Currently no explicit way to hook into how to cope with your custom data
* Currently no way to insert an xml declaration line
Changelog
---------
1.7.0 - 16 April, 2020
* Use collections.abc to avoid deprecation warning. Thanks @mangin.
* This library no longer supports Python2 and is only supported for
Python3.6+. Note that the library should still work in Python3.5 as I
have not used f-strings, but the framework I use for the tests is only 3.6+.
1.6.1 - August 27, 2019
* Include readme and LICENSE in the package
1.6 - April 27, 2018
* No code changes
* changed the licence to MIT
* Added more metadata to pypi
* Enabled travis ci
* Updated the tests slightly
1.5
* No changelog was kept before this point.
Platform: UNKNOWN
Classifier: Development Status :: 5 - Production/Stable
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 2.7
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: Text Processing :: Markup :: XML
Provides-Extra: tests
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1586988232.0
dict2xml-1.7.0/README.rst 0000644 0000765 0000024 00000004260 00000000000 016572 0 ustar 00stephenmoore staff 0000000 0000000 dict2xml
========
Super Simple utility to convert a python dictionary into an xml string
Installation
------------
Make youself a virtualenv and do the following::
$ pip install .
Or if you want to develop on dict2xml, recommended commands are::
$ pip install -e .
$ pip install -e ".[tests]"
Or if you don't want to install from source::
$ pip install dict2xml
example
-------
.. code-block:: python
from dict2xml import dict2xml
data = {
'a': 1,
'b': [2, 3],
'c': {
'd': [
{'p': 9},
{'o': 10}
],
'e': 7
}
}
print dict2xml(data, wrap="all", indent=" ")
Output
------
.. code-block:: xml
1
2
3
9
10
7
methods
-------
``dict2xml.dict2xml(data, *args, **kwargs)``
Equivalent to:
.. code-block:: python
dict2xml.Converter(*args, **kwargs).build(data)
``dict2xml.Converter(wrap="", indent=" ", newlines=True)``
Knows how to convert a dictionary into an xml string
* wrap: Wraps the entire tree in this tag
* indent: Amount to prefix each line for each level of nesting
* newlines: Whether or not to use newlines
``dict2xml.Converter.build(data)``
Instance method on Converter that takes in the data and creates the xml string
Limitations
-----------
* No attributes on elements
* Currently no explicit way to hook into how to cope with your custom data
* Currently no way to insert an xml declaration line
Changelog
---------
1.7.0 - 16 April, 2020
* Use collections.abc to avoid deprecation warning. Thanks @mangin.
* This library no longer supports Python2 and is only supported for
Python3.6+. Note that the library should still work in Python3.5 as I
have not used f-strings, but the framework I use for the tests is only 3.6+.
1.6.1 - August 27, 2019
* Include readme and LICENSE in the package
1.6 - April 27, 2018
* No code changes
* changed the licence to MIT
* Added more metadata to pypi
* Enabled travis ci
* Updated the tests slightly
1.5
* No changelog was kept before this point.
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 011452 x ustar 00 0000000 0000000 28 mtime=1586988343.1158354
dict2xml-1.7.0/dict2xml/ 0000755 0000765 0000024 00000000000 00000000000 016627 5 ustar 00stephenmoore staff 0000000 0000000 ././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1586987523.0
dict2xml-1.7.0/dict2xml/__init__.py 0000644 0000765 0000024 00000000352 00000000000 020740 0 ustar 00stephenmoore staff 0000000 0000000 from dict2xml.logic import Converter, Node
def dict2xml(data, *args, **kwargs):
"""Return an XML string of a Python dict object."""
return Converter(*args, **kwargs).build(data)
__all__ = ["dict2xml", "Converter", "Node"]
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1586987696.0
dict2xml-1.7.0/dict2xml/logic.py 0000644 0000765 0000024 00000021301 00000000000 020273 0 ustar 00stephenmoore staff 0000000 0000000 import collections.abc
import collections
import re
start_ranges = "|".join(
"[{0}]".format(r)
for r in [
"\xC0-\xD6",
"\xD8-\xF6",
"\xF8-\u02FF",
"\u0370-\u037D",
"\u037F-\u1FFF",
"\u200C-\u200D",
"\u2070-\u218F",
"\u2C00-\u2FEF",
"\u3001-\uD7FF",
"\uF900-\uFDCF",
"\uFDF0-\uFFFD",
]
)
NameStartChar = re.compile(r"(:|[A-Z]|_|[a-z]|{0})".format(start_ranges))
NameChar = re.compile(r"(\-|\.|[0-9]|\xB7|[\u0300-\u036F]|[\u203F-\u2040])")
########################
### NODE
########################
class Node(object):
"""
Represents each tag in the tree
Each node has _either_ a single value or one or more children
If it has a value:
The serialized result is <%(tag)s>%(value)s%(tag)s>
If it has children:
The serialized result is
<%(wrap)s>
%(children)s
%(wrap)s>
Which one it is depends on the implementation of self.convert
"""
# A mapping of characters to treat as escapable entities and their replacements
entities = [("&", "&"), ("<", "<"), (">", ">")]
def __init__(self, wrap="", tag="", data=None, iterables_repeat_wrap=True):
self.tag = self.sanitize_element(tag)
self.wrap = self.sanitize_element(wrap)
self.data = data
self.type = self.determine_type()
self.iterables_repeat_wrap = iterables_repeat_wrap
if self.type == "flat" and isinstance(self.data, str):
# Make sure we deal with entities
for entity, replacement in self.entities:
self.data = self.data.replace(entity, replacement)
def serialize(self, indenter):
"""Returns the Node serialized as an xml string"""
# Determine the start and end of this node
wrap = self.wrap
end, start = "", ""
if wrap:
end = "{0}>".format(wrap)
start = "<{0}>".format(wrap)
# Convert the data attached in this node into a value and children
value, children = self.convert()
# Determine the content of the node (essentially the children as a string value)
content = ""
if children:
if self.type != "iterable":
# Non-iterable wraps all it's children in the same tag
content = indenter((c.serialize(indenter) for c in children), wrap)
else:
if self.iterables_repeat_wrap:
# Iterables repeat the wrap for each child
result = []
for c in children:
content = c.serialize(indenter)
if c.type == "flat":
# Child with value, it already is surrounded by the tag
result.append(content)
else:
# Child with children of it's own, they need to be wrapped by start and end
content = indenter([content], True)
result.append("".join((start, content, end)))
# We already have what we want, return the indented result
return indenter(result, False)
else:
result = []
for c in children:
result.append(c.serialize(indenter))
return "".join([start, indenter(result, True), end])
# If here, either:
# * Have a value
# * Or this node is not an iterable
return "".join((start, value, content, end))
def determine_type(self):
"""
Return the type of the data on this node as an identifying string
* Iterable : Supports "for item in data"
* Mapping : Supports "for key in data: value = data[key]"
* flat : A string or something that isn't iterable or a mapping
"""
data = self.data
if isinstance(data, str):
return "flat"
elif isinstance(data, collections.abc.Mapping):
return "mapping"
elif isinstance(data, collections.abc.Iterable):
return "iterable"
else:
return "flat"
def convert(self):
"""
Convert data on this node into a (value, children) tuple depending on the type of the data
If the type is :
* flat : Use self.tag to surround the value. value
* mapping : Return a list of tags where the key for each child is the wrap for that node
* iterable : Return a list of Nodes where self.wrap is the tag for that node
"""
val = ""
typ = self.type
data = self.data
children = []
if typ == "mapping":
sorted_data = data
if not isinstance(data, collections.OrderedDict):
sorted_data = sorted(data)
for key in sorted_data:
item = data[key]
children.append(
Node(key, "", item, iterables_repeat_wrap=self.iterables_repeat_wrap)
)
elif typ == "iterable":
for item in data:
children.append(
Node("", self.wrap, item, iterables_repeat_wrap=self.iterables_repeat_wrap,)
)
else:
val = str(data)
if self.tag:
val = "<{0}>{1}{2}>".format(self.tag, val, self.tag)
return val, children
@staticmethod
def sanitize_element(wrap):
"""
Convert `wrap` into a valid tag name applying the XML Naming Rules.
* Names can contain letters, numbers, and other characters
* Names cannot start with a number or punctuation character
* Names cannot start with the letters xml (or XML, or Xml, etc)
* Names cannot contain spaces
* Any name can be used, no words are reserved.
:ref: http://www.w3.org/TR/REC-xml/#NT-NameChar
"""
if wrap and isinstance(wrap, str):
if wrap.lower().startswith("xml"):
wrap = "_" + wrap
return "".join(
["_" if not NameStartChar.match(wrap) else ""]
+ ["_" if not (NameStartChar.match(c) or NameChar.match(c)) else c for c in wrap]
)
else:
return wrap
########################
### CONVERTER
########################
class Converter(object):
"""Logic for creating a Node tree and serialising that tree into a string"""
def __init__(self, wrap=None, indent=" ", newlines=True):
"""
wrap: The tag that the everything else will be contained within
indent: The string that is multiplied at the start of each new line, to represent each level of nesting
newlines: A boolean specifying whether we want each tag on a new line.
Note that indent only works if newlines is True
"""
self.wrap = wrap
self.indent = indent
self.newlines = newlines
def _make_indenter(self):
"""Returns a function that given a list of strings, will return that list as a single, indented, string"""
indent = self.indent
newlines = self.newlines
if not newlines:
# No newlines, don't care about indentation
ret = lambda nodes, wrapped: "".join(nodes)
else:
if not indent:
indent = ""
def eachline(nodes):
"""Yield each line in each node"""
for node in nodes:
for line in node.split("\n"):
yield line
def ret(nodes, wrapped):
"""
Indent nodes depending on value of wrapped and indent
If not wrapped, then don't indent
Otherwise,
Seperate each child by a newline
and indent each line in the child by one indent unit
"""
if wrapped:
seperator = "\n{0}".format(indent)
surrounding = "\n{0}{{0}}\n".format(indent)
else:
seperator = "\n"
surrounding = "{0}"
return surrounding.format(seperator.join(eachline(nodes)))
return ret
def build(self, data, iterables_repeat_wrap=True):
"""Create a Node tree from the data and return it as a serialized xml string"""
indenter = self._make_indenter()
return Node(
wrap=self.wrap, data=data, iterables_repeat_wrap=iterables_repeat_wrap
).serialize(indenter)
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 011452 x ustar 00 0000000 0000000 28 mtime=1586988343.1176314
dict2xml-1.7.0/dict2xml.egg-info/ 0000755 0000765 0000024 00000000000 00000000000 020321 5 ustar 00stephenmoore staff 0000000 0000000 ././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1586988342.0
dict2xml-1.7.0/dict2xml.egg-info/PKG-INFO 0000644 0000765 0000024 00000007363 00000000000 021427 0 ustar 00stephenmoore staff 0000000 0000000 Metadata-Version: 2.1
Name: dict2xml
Version: 1.7.0
Summary: Small utility to convert a python dictionary into an XML string
Home-page: http://github.com/delfick/python-dict2xml
Author: Stephen Moore
Author-email: stephen@delfick.com
License: MIT
Description: dict2xml
========
Super Simple utility to convert a python dictionary into an xml string
Installation
------------
Make youself a virtualenv and do the following::
$ pip install .
Or if you want to develop on dict2xml, recommended commands are::
$ pip install -e .
$ pip install -e ".[tests]"
Or if you don't want to install from source::
$ pip install dict2xml
example
-------
.. code-block:: python
from dict2xml import dict2xml
data = {
'a': 1,
'b': [2, 3],
'c': {
'd': [
{'p': 9},
{'o': 10}
],
'e': 7
}
}
print dict2xml(data, wrap="all", indent=" ")
Output
------
.. code-block:: xml
1
2
3
9
10
7
methods
-------
``dict2xml.dict2xml(data, *args, **kwargs)``
Equivalent to:
.. code-block:: python
dict2xml.Converter(*args, **kwargs).build(data)
``dict2xml.Converter(wrap="", indent=" ", newlines=True)``
Knows how to convert a dictionary into an xml string
* wrap: Wraps the entire tree in this tag
* indent: Amount to prefix each line for each level of nesting
* newlines: Whether or not to use newlines
``dict2xml.Converter.build(data)``
Instance method on Converter that takes in the data and creates the xml string
Limitations
-----------
* No attributes on elements
* Currently no explicit way to hook into how to cope with your custom data
* Currently no way to insert an xml declaration line
Changelog
---------
1.7.0 - 16 April, 2020
* Use collections.abc to avoid deprecation warning. Thanks @mangin.
* This library no longer supports Python2 and is only supported for
Python3.6+. Note that the library should still work in Python3.5 as I
have not used f-strings, but the framework I use for the tests is only 3.6+.
1.6.1 - August 27, 2019
* Include readme and LICENSE in the package
1.6 - April 27, 2018
* No code changes
* changed the licence to MIT
* Added more metadata to pypi
* Enabled travis ci
* Updated the tests slightly
1.5
* No changelog was kept before this point.
Platform: UNKNOWN
Classifier: Development Status :: 5 - Production/Stable
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 2.7
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: Text Processing :: Markup :: XML
Provides-Extra: tests
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1586988342.0
dict2xml-1.7.0/dict2xml.egg-info/SOURCES.txt 0000644 0000765 0000024 00000000407 00000000000 022206 0 ustar 00stephenmoore staff 0000000 0000000 LICENSE
MANIFEST.in
README.rst
pyproject.toml
readme.rst
setup.py
dict2xml/__init__.py
dict2xml/logic.py
dict2xml.egg-info/PKG-INFO
dict2xml.egg-info/SOURCES.txt
dict2xml.egg-info/dependency_links.txt
dict2xml.egg-info/requires.txt
dict2xml.egg-info/top_level.txt ././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1586988342.0
dict2xml-1.7.0/dict2xml.egg-info/dependency_links.txt 0000644 0000765 0000024 00000000001 00000000000 024367 0 ustar 00stephenmoore staff 0000000 0000000
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1586988342.0
dict2xml-1.7.0/dict2xml.egg-info/requires.txt 0000644 0000765 0000024 00000000051 00000000000 022715 0 ustar 00stephenmoore staff 0000000 0000000
[tests]
noseOfYeti==2.0.1
pytest==5.3.1
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1586988342.0
dict2xml-1.7.0/dict2xml.egg-info/top_level.txt 0000644 0000765 0000024 00000000011 00000000000 023043 0 ustar 00stephenmoore staff 0000000 0000000 dict2xml
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1586987523.0
dict2xml-1.7.0/pyproject.toml 0000644 0000765 0000024 00000000164 00000000000 020016 0 ustar 00stephenmoore staff 0000000 0000000 [tool.black]
line-length = 100
include = '\.py$'
exclude = '''
/(
\.git
| \.tox
| dist
| tools
)/
'''
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 011452 x ustar 00 0000000 0000000 28 mtime=1586988343.1185412
dict2xml-1.7.0/setup.cfg 0000644 0000765 0000024 00000000046 00000000000 016722 0 ustar 00stephenmoore staff 0000000 0000000 [egg_info]
tag_build =
tag_date = 0
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1586988214.0
dict2xml-1.7.0/setup.py 0000644 0000765 0000024 00000001774 00000000000 016624 0 ustar 00stephenmoore staff 0000000 0000000 from setuptools import setup
# fmt: off
# Setup the project
setup(
name = "dict2xml"
, version = '1.7.0'
, packages = ['dict2xml']
, extras_require =
{ 'tests' :
[ "noseOfYeti==2.0.1"
, "pytest==5.3.1"
]
}
, classifiers =
[ "Development Status :: 5 - Production/Stable"
, "License :: OSI Approved :: MIT License"
, "Operating System :: OS Independent"
, "Programming Language :: Python"
, "Programming Language :: Python :: 2.7"
, "Programming Language :: Python :: 3"
, "Topic :: Software Development :: Libraries :: Python Modules"
, "Topic :: Text Processing :: Markup :: XML"
]
# metadata
, url = "http://github.com/delfick/python-dict2xml"
, author = "Stephen Moore"
, author_email = "stephen@delfick.com"
, description = "Small utility to convert a python dictionary into an XML string"
, long_description = open("README.rst").read()
, license = "MIT"
)
# fmt: on