dict2xml-1.7.5/pytest.ini0000644000000000000000000000012113615410400012215 0ustar00[pytest] addopts = "--tb=short" testpaths = tests console_output_style = classic dict2xml-1.7.5/run.sh0000755000000000000000000000236113615410400011337 0ustar00#!/bin/bash cwd="$(pwd)" # Bash does not make it easy to find where this file is # Here I'm making it so it doesn't matter what directory you are in # when you execute this script. And it doesn't matter whether you're # executing a symlink to this script # Note the `-h` in the while loop asks if this path is a symlink pushd . >'/dev/null' SCRIPT_PATH="${BASH_SOURCE[0]:-$0}" while [ -h "$SCRIPT_PATH" ]; do cd "$(dirname -- "$SCRIPT_PATH")" SCRIPT_PATH="$(readlink -f -- "$SCRIPT_PATH")" done cd "$(dirname -- "$SCRIPT_PATH")" >'/dev/null' # We use noseOfYeti here, so let's make black compatible with it export NOSE_OF_YETI_BLACK_COMPAT=true HANDLED=0 # Special case activate to make the virtualenv active in this session if [[ "$0" != "$BASH_SOURCE" ]]; then HANDLED=1 if [[ "activate" == "$1" ]]; then VENVSTARTER_ONLY_MAKE_VENV=1 ./tools/venv source ./tools/.python/bin/activate cd "$cwd" else echo "only say \`source run.sh activate\`" fi fi if [[ $HANDLED != 1 ]]; then if [[ "$#" == "1" && "$1" == "activate" ]]; then if [[ "$0" = "$BASH_SOURCE" ]]; then echo "You need to run as 'source ./run.sh $1'" exit 1 fi fi exec ./tools/venv "$@" fi dict2xml-1.7.5/test.sh0000755000000000000000000000020113615410400011501 0ustar00#!/bin/bash set -e export TESTS_CHDIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" cd $TESTS_CHDIR ./tools/venv tests -q $@ dict2xml-1.7.5/dict2xml/__init__.py0000644000000000000000000000045513615410400014035 0ustar00from dict2xml.logic import Converter, DataSorter, Node from .version import VERSION def dict2xml(data, *args, **kwargs): """Return an XML string of a Python dict object.""" return Converter(*args, **kwargs).build(data) __all__ = ["dict2xml", "Converter", "Node", "VERSION", "DataSorter"] dict2xml-1.7.5/dict2xml/logic.py0000644000000000000000000002354413615410400013377 0ustar00import collections import collections.abc 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 DataSorter: """ Used to sort a map of data depending on it's type """ def keys_from(self, data): sorted_data = data if not isinstance(data, collections.OrderedDict): sorted_data = sorted(data) return sorted_data class always: def keys_from(self, data): return sorted(data) class never: def keys_from(self, data): return data 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 If it has children: The serialized result is <%(wrap)s> %(children)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, closed_tags_for=None, data_sorter=None, ): self.tag = self.sanitize_element(tag) self.wrap = self.sanitize_element(wrap) self.data = data self.type = self.determine_type() self.data_sorter = data_sorter if data_sorter is not None else DataSorter() self.closed_tags_for = closed_tags_for 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 = "".format(wrap) start = "<{0}>".format(wrap) if self.closed_tags_for and self.data in self.closed_tags_for: return "<{0}/>".format(self.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_keys = self.data_sorter.keys_from(data) for key in sorted_keys: item = data[key] children.append( Node( key, "", item, iterables_repeat_wrap=self.iterables_repeat_wrap, closed_tags_for=self.closed_tags_for, data_sorter=self.data_sorter, ) ) elif typ == "iterable": for item in data: children.append( Node( "", self.wrap, item, iterables_repeat_wrap=self.iterables_repeat_wrap, closed_tags_for=self.closed_tags_for, data_sorter=self.data_sorter, ) ) else: val = str(data) if self.tag: val = "<{0}>{1}".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, closed_tags_for=None, data_sorter=None): """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, closed_tags_for=closed_tags_for, data_sorter=data_sorter, ).serialize(indenter) dict2xml-1.7.5/dict2xml/version.py0000644000000000000000000000002213615410400013751 0ustar00VERSION = "1.7.5" dict2xml-1.7.5/tests/__init__.py0000644000000000000000000000000013615410400013433 0ustar00dict2xml-1.7.5/tests/build_test.py0000644000000000000000000000612413615410400014047 0ustar00# coding: spec import json import os from textwrap import dedent from unittest import mock import pytest from dict2xml import Converter, dict2xml examples = os.path.join(os.path.dirname(__file__), "examples") describe "Build": describe "Convenience Function": it "Creates a Converter with *args and **kwargs and calls build on it with provided data": data = mock.Mock(name="data") serialized = mock.Mock(name="serialized") converter = mock.Mock(name="converter") converter.build.return_value = serialized FakeConverter = mock.Mock(name="Converter", return_value=converter) with mock.patch("dict2xml.Converter", FakeConverter): assert dict2xml(data, 1, 2, 3, a=5, b=8) is serialized FakeConverter.assert_called_once_with(1, 2, 3, a=5, b=8) converter.build.assert_called_once_with(data) describe "Just Working": @pytest.fixture() def assertResult(self): def assertResult(result, **kwargs): data = {"a": [1, 2, 3], "b": {"c": "d", "e": {"f": "g"}}, "d": 1} converter = Converter(wrap="all", **kwargs) print(converter.build(data)) assert dedent(result).strip() == converter.build(data) return assertResult it "with both indentation and newlines", assertResult: expected = """ 1 2 3 d g 1 """ assertResult(expected, indent=" ", newlines=True) it "with just newlines", assertResult: expected = """ 1 2 3 d g 1 """ assertResult(expected, indent=None, newlines=True) it "with just indentation", assertResult: # Indentation requires newlines to work expected = "123dg1" assertResult(expected, indent=" ", newlines=False) it "with no whitespace", assertResult: expected = "123dg1" assertResult(expected, indent=None, newlines=False) it "works on a massive, complex dictionary": with open(os.path.join(examples, "python_dict.json"), "r") as fle: data = json.load(fle) with open(os.path.join(examples, "result.xml"), "r") as fle: result = fle.read() converter = Converter(wrap="all", indent=" ", newlines=True) assert dedent(result).strip() == converter.build(data) dict2xml-1.7.5/tests/converter_test.py0000644000000000000000000001713513615410400014763 0ustar00# coding: spec from textwrap import dedent from unittest import mock import pytest from dict2xml import Converter describe "Converter": describe "Building": it "creates an indenter, a node, and then calls serialize on the node with the indenter": wrap = mock.Mock("wrap") indent = mock.Mock("indent") newlines = mock.Mock("newlines") converter = Converter(wrap, indent, newlines) node = mock.Mock(name="node") FakeNode = mock.Mock(name="Node", return_value=node) serialized = mock.Mock(name="serialized") node.serialize.return_value = serialized indenter = mock.Mock(name="indenter") make_indenter = mock.Mock(name="make_indenter", return_value=indenter) mip = mock.patch.object(converter, "_make_indenter", make_indenter) fnp = mock.patch("dict2xml.logic.Node", FakeNode) data = mock.Mock(name="data") with mip, fnp: assert converter.build(data) is serialized FakeNode.assert_called_once_with( wrap=wrap, data=data, iterables_repeat_wrap=True, closed_tags_for=None, data_sorter=None, ) node.serialize.assert_called_once_with(indenter) it "doesn't repeat the wrap if iterables_repeat_wrap is False": example = { "array": [ {"item": {"string1": "string", "string2": "string"}}, {"item": {"string1": "other string", "string2": "other string"}}, ] } result = Converter("").build(example, iterables_repeat_wrap=False) assert ( result == dedent( """ string string other string other string """ ).strip() ) it "can produce self closing tags": example = { "item1": None, "item2": {"string1": "", "string2": None}, "item3": "special", } result = Converter("").build(example, closed_tags_for=[None]) assert ( result == dedent( """ special """ ).strip() ) result = Converter("").build(example, closed_tags_for=[None, ""]) assert ( result == dedent( """ special """ ).strip() ) result = Converter("").build(example, closed_tags_for=["special"]) print(result) assert ( result == dedent( """ None None """ ).strip() ) describe "Making indentation function": @pytest.fixture() def V(self): class V: with_indent = Converter(indent=" ", newlines=True) without_indent = Converter(indent="", newlines=True) without_newlines = Converter(newlines=False) def assertIndenter(self, indenter, nodes, wrap, expected): result = "".join([wrap, indenter(nodes, wrap), wrap]) assert result == expected.strip() return V() describe "No newlines": it "joins nodes with empty string", V: indenter = V.without_newlines._make_indenter() assert indenter(["a", "b", "c"], True) == "abc" assert indenter(["d", "e", "f"], False) == "def" describe "With newlines": describe "No indentation": it "joins with newlines and never indents", V: # Wrap is added to expected output via test_indenter indenter = V.without_indent._make_indenter() V.assertIndenter( indenter, ["a", "b", "c"], "<>", dedent( """ <> a b c <>""" ), ) describe "With indentation": it "joins with newlines and indents if there is a wrapping tag", V: # Wrap is added to expected output via test_indenter indenter = V.with_indent._make_indenter() V.assertIndenter( indenter, ["a", "b", "c"], "<>", dedent( """ <> a b c <>""" ), ) it "joins with newlines but doesn't indent if no wrapping tag", V: indenter = V.with_indent._make_indenter() V.assertIndenter( indenter, ["a", "b", "c"], "", dedent( """ a b c""" ), ) it "reindents each new line", V: node1 = dedent( """ a b c d e """ ).strip() node2 = "f" node3 = dedent( """ f g h """ ).strip() # Wrap is added to expected output via test_indenter indenter = V.with_indent._make_indenter() V.assertIndenter( indenter, [node1, node2, node3], "<>", dedent( """ <> a b c d e f f g h <> """ ), ) dict2xml-1.7.5/tests/node_test.py0000644000000000000000000002241713615410400013700 0ustar00# coding: spec import collections import collections.abc from unittest import mock from dict2xml import DataSorter, Node describe "Node": it "determines type at instantiation": assert Node(data={}).type == "mapping" assert Node(data=[]).type == "iterable" for d in ["", "asdf", "", "asdf", 0, 1, False, True]: assert Node(data=d).type == "flat" describe "Handling entities": it "will change string data to take entities into account": node = Node(data="<2&a>") assert node.data == "<2&a>" describe "Determining type": def assertType(self, *datas, **kwargs): expected = kwargs.get("expected", None) for d in datas: assert Node(data=d).determine_type() == expected it "says strings are flat": self.assertType("", "asdf", "", "asdf", expected="flat") it "says numbers and booleans are flat": self.assertType(0, 1, False, True, expected="flat") it "says anything that implements __iter__ is an iterable": class IterableObject(object): def __iter__(s): return [] self.assertType((), [], set(), IterableObject(), expected="iterable") it "says anything that is a dict or subclass of collections.Mapping is a mapping": class MappingObject(collections.abc.Mapping): def __len__(s): return 0 def __iter__(s): return [] def __getitem__(s, key): return key self.assertType({}, MappingObject(), expected="mapping") it "can't determine if an object is a mapping if it isn't sublass of collections.Mapping": # Would be great if possible, but doesn't seem to be :( class WantsToBeMappingObject(object): def __iter__(s): return [] def __getitem__(s, key): return key self.assertType(WantsToBeMappingObject(), expected="iterable") describe "Conversion": it "returns list of Nodes with key as wrap and item as data if type is mapping": called = [] nodes = [mock.Mock(name="n{0}".format(i)) for i in range(3)] def N(*args, **kwargs): called.append(1) return nodes[len(called) - 1] ds = DataSorter() irw = mock.Mock("irw") ctf = mock.Mock("ctf") FakeNode = mock.Mock(name="Node", side_effect=N) with mock.patch("dict2xml.logic.Node", FakeNode): data = dict(a=1, b=2, c=3) result = Node( data=data, iterables_repeat_wrap=irw, closed_tags_for=ctf, data_sorter=ds ).convert() assert result == ("", nodes) assert FakeNode.mock_calls == [ mock.call( "a", "", 1, iterables_repeat_wrap=irw, closed_tags_for=ctf, data_sorter=ds ), mock.call( "b", "", 2, iterables_repeat_wrap=irw, closed_tags_for=ctf, data_sorter=ds ), mock.call( "c", "", 3, iterables_repeat_wrap=irw, closed_tags_for=ctf, data_sorter=ds ), ] it "respects the order of an OrderedDict": called = [] nodes = [mock.Mock(name="n{0}".format(i)) for i in range(3)] def N(*args, **kwargs): called.append(1) return nodes[len(called) - 1] ds = DataSorter() irw = mock.Mock("irw") ctf = mock.Mock("ctf") FakeNode = mock.Mock(name="Node", side_effect=N) with mock.patch("dict2xml.logic.Node", FakeNode): data = collections.OrderedDict([("b", 2), ("c", 3), ("a", 1)]) result = Node( data=data, iterables_repeat_wrap=irw, closed_tags_for=ctf, data_sorter=ds ).convert() assert result == ("", nodes) assert FakeNode.mock_calls == [ mock.call( "b", "", 2, iterables_repeat_wrap=irw, closed_tags_for=ctf, data_sorter=ds ), mock.call( "c", "", 3, iterables_repeat_wrap=irw, closed_tags_for=ctf, data_sorter=ds ), mock.call( "a", "", 1, iterables_repeat_wrap=irw, closed_tags_for=ctf, data_sorter=ds ), ] it "can be told to also sort OrderedDict": called = [] nodes = [mock.Mock(name="n{0}".format(i)) for i in range(3)] def N(*args, **kwargs): called.append(1) return nodes[len(called) - 1] ds = DataSorter.always() irw = mock.Mock("irw") ctf = mock.Mock("ctf") FakeNode = mock.Mock(name="Node", side_effect=N) with mock.patch("dict2xml.logic.Node", FakeNode): data = collections.OrderedDict([("b", 2), ("c", 3), ("a", 1)]) result = Node( data=data, iterables_repeat_wrap=irw, closed_tags_for=ctf, data_sorter=ds ).convert() assert result == ("", nodes) assert FakeNode.mock_calls == [ mock.call( "a", "", 1, iterables_repeat_wrap=irw, closed_tags_for=ctf, data_sorter=ds ), mock.call( "b", "", 2, iterables_repeat_wrap=irw, closed_tags_for=ctf, data_sorter=ds ), mock.call( "c", "", 3, iterables_repeat_wrap=irw, closed_tags_for=ctf, data_sorter=ds ), ] it "can be told to never sort": called = [] nodes = [mock.Mock(name="n{0}".format(i)) for i in range(3)] def N(*args, **kwargs): called.append(1) return nodes[len(called) - 1] ds = DataSorter.never() irw = mock.Mock("irw") ctf = mock.Mock("ctf") FakeNode = mock.Mock(name="Node", side_effect=N) with mock.patch("dict2xml.logic.Node", FakeNode): data = {"c": 3, "a": 1, "b": 2} result = Node( data=data, iterables_repeat_wrap=irw, closed_tags_for=ctf, data_sorter=ds ).convert() assert result == ("", nodes) assert FakeNode.mock_calls == [ mock.call( "c", "", 3, iterables_repeat_wrap=irw, closed_tags_for=ctf, data_sorter=ds ), mock.call( "a", "", 1, iterables_repeat_wrap=irw, closed_tags_for=ctf, data_sorter=ds ), mock.call( "b", "", 2, iterables_repeat_wrap=irw, closed_tags_for=ctf, data_sorter=ds ), ] it "returns list of Nodes with wrap as tag and item as data if type is iterable": called = [] nodes = [mock.Mock(name="n{0}".format(i)) for i in range(3)] def N(*args, **kwargs): called.append(1) return nodes[len(called) - 1] ds = DataSorter() irw = mock.Mock("irw") ctf = mock.Mock("ctf") FakeNode = mock.Mock(name="Node", side_effect=N) with mock.patch("dict2xml.logic.Node", FakeNode): data = [1, 2, 3] result = Node( data=data, iterables_repeat_wrap=irw, closed_tags_for=ctf, data_sorter=ds ).convert() assert result == ("", nodes) assert FakeNode.mock_calls == [ mock.call( "", "", 1, iterables_repeat_wrap=irw, closed_tags_for=ctf, data_sorter=ds ), mock.call( "", "", 2, iterables_repeat_wrap=irw, closed_tags_for=ctf, data_sorter=ds ), mock.call( "", "", 3, iterables_repeat_wrap=irw, closed_tags_for=ctf, data_sorter=ds ), ] it "returns data enclosed in tags made from self.tag if not iterable or mapping": tag = "thing" results = [] for d in [0, 1, "", "", "asdf", "qwer", False, True]: val, children = Node(tag=tag, data=d).convert() assert len(children) == 0 results.append(val) assert results == [ "0", "1", "", "", "asdf", "qwer", "False", "True", ] it "returns data as is if not iterable or mapping and no self.tag": tag = "" results = [] for d in [0, 1, "", "", "asdf", "qwer", False, True]: val, children = Node(tag=tag, data=d).convert() assert len(children) == 0 results.append(val) assert results == ["0", "1", "", "", "asdf", "qwer", "False", "True"] dict2xml-1.7.5/tests/examples/__init__.py0000644000000000000000000000000013615410400015251 0ustar00dict2xml-1.7.5/tests/examples/python_dict.json0000644000000000000000000000754013615410400016377 0ustar00{ "web-app": { "servlet": [ { "servlet-name": "cofaxCDS", "servlet-class": "org.cofax.cds.CDSServlet", "init-param": { "configGlossary:installationAt": "Philadelphia PA", "configGlossary:adminEmail": "ksm@pobox.com", "configGlossary:poweredBy": "Cofax", "configGlossary:poweredByIcon": "/images/cofax.gif", "configGlossary:staticPath": "/content/static", "templateProcessorClass": "org.cofax.WysiwygTemplate", "templateLoaderClass": "org.cofax.FilesTemplateLoader", "templatePath": "templates", "templateOverridePath": "", "defaultListTemplate": "listTemplate.htm", "defaultFileTemplate": "articleTemplate.htm", "useJSP": false, "jspListTemplate": "listTemplate.jsp", "jspFileTemplate": "articleTemplate.jsp", "cachePackageTagsTrack": 200, "cachePackageTagsStore": 200, "cachePackageTagsRefresh": 60, "cacheTemplatesTrack": 100, "cacheTemplatesStore": 50, "cacheTemplatesRefresh": 15, "cachePagesTrack": 200, "cachePagesStore": 100, "cachePagesRefresh": 10, "cachePagesDirtyRead": 10, "searchEngineListTemplate": "forSearchEnginesList.htm", "searchEngineFileTemplate": "forSearchEngines.htm", "searchEngineRobotsDb": "WEB-INF/robots.db", "useDataStore": true, "dataStoreClass": "org.cofax.SqlDataStore", "redirectionClass": "org.cofax.SqlRedirection", "dataStoreName": "cofax", "dataStoreDriver": "com.microsoft.jdbc.sqlserver.SQLServerDriver", "dataStoreUrl": "jdbc:microsoft:sqlserver://LOCALHOST:1433;DatabaseName=goon", "dataStoreUser": "sa", "dataStorePassword": "dataStoreTestQuery", "dataStoreTestQuery": "SET NOCOUNT ON;select test='test';", "dataStoreLogFile": "/usr/local/tomcat/logs/datastore.log", "dataStoreInitConns": 10, "dataStoreMaxConns": 100, "dataStoreConnUsageLimit": 100, "dataStoreLogLevel": "debug", "maxUrlLength": 500 } }, { "servlet-name": "cofaxEmail", "servlet-class": "org.cofax.cds.EmailServlet", "init-param": { "mailHost": "mail1", "mailHostOverride": "mail2" } }, { "servlet-name": "cofaxAdmin", "servlet-class": "org.cofax.cds.AdminServlet" }, { "servlet-name": "fileServlet", "servlet-class": "org.cofax.cds.FileServlet" }, { "servlet-name": "cofaxTools", "servlet-class": "org.cofax.cms.CofaxToolsServlet", "init-param": { "templatePath": "toolstemplates/", "log": 1, "logLocation": "/usr/local/tomcat/logs/CofaxTools.log", "logMaxSize": "", "dataLog": 1, "dataLogLocation": "/usr/local/tomcat/logs/dataLog.log", "dataLogMaxSize": "", "removePageCache": "/content/admin/remove?cache=pages&id=", "removeTemplateCache": "/content/admin/remove?cache=templates&id=", "fileTransferFolder": "/usr/local/tomcat/webapps/content/fileTransferFolder", "lookInContext": 1, "adminGroupID": 4, "betaServer": true } } ], "servlet-mapping": { "cofaxCDS": "/", "cofaxEmail": "/cofaxutil/aemail/*", "cofaxAdmin": "/admin/*", "fileServlet": "/static/*", "cofaxTools": "/tools/*" }, "taglib": { "taglib-uri": "cofax.tld", "taglib-location": "/WEB-INF/tlds/cofax.tld" } }, "lessthan": "<", "entitylist": [ { "ampersand": "&" }, { "mix": ">p<" } ], "3badtagstart": "x", "xml_is_an_invalid_prefix": "x", "Xml_with_other_case_is_an_invalid_prefix": "x" } dict2xml-1.7.5/tests/examples/result.xml0000644000000000000000000001170613615410400015217 0ustar00 <_3badtagstart>x <_Xml_with_other_case_is_an_invalid_prefix>x & >p< < 60 200 200 10 10 100 200 15 50 100 ksm@pobox.com Philadelphia PA Cofax /images/cofax.gif /content/static org.cofax.SqlDataStore 100 com.microsoft.jdbc.sqlserver.SQLServerDriver 10 /usr/local/tomcat/logs/datastore.log debug 100 cofax dataStoreTestQuery SET NOCOUNT ON;select test='test'; jdbc:microsoft:sqlserver://LOCALHOST:1433;DatabaseName=goon sa articleTemplate.htm listTemplate.htm articleTemplate.jsp listTemplate.jsp 500 org.cofax.SqlRedirection forSearchEngines.htm forSearchEnginesList.htm WEB-INF/robots.db org.cofax.FilesTemplateLoader templates org.cofax.WysiwygTemplate True False org.cofax.cds.CDSServlet cofaxCDS mail1 mail2 org.cofax.cds.EmailServlet cofaxEmail org.cofax.cds.AdminServlet cofaxAdmin org.cofax.cds.FileServlet fileServlet 4 True 1 /usr/local/tomcat/logs/dataLog.log /usr/local/tomcat/webapps/content/fileTransferFolder 1 /usr/local/tomcat/logs/CofaxTools.log 1 /content/admin/remove?cache=pages&id= /content/admin/remove?cache=templates&id= toolstemplates/ org.cofax.cms.CofaxToolsServlet cofaxTools /admin/* / /cofaxutil/aemail/* /tools/* /static/* /WEB-INF/tlds/cofax.tld cofax.tld <_xml_is_an_invalid_prefix>x dict2xml-1.7.5/tools/bootstrap_venvstarter.py0000644000000000000000000000155313615410400016370 0ustar00import os import runpy import sys from pathlib import Path deps_dir = Path(__file__).parent / "deps" if not deps_dir.exists(): deps_dir.mkdir() if not (deps_dir / "venvstarter.py").exists(): if "PIP_REQUIRE_VIRTUALENV" in os.environ: del os.environ["PIP_REQUIRE_VIRTUALENV"] os.system(f"{sys.executable} -m pip install venvstarter -t {deps_dir}") venvstarter_module = runpy.run_path(str(deps_dir / "venvstarter.py")) wanted_version = "0.12.1" upgrade = False VERSION = venvstarter_module.get("VERSION") if VERSION is None: upgrade = True else: Version = venvstarter_module["Version"] if Version(VERSION) != Version(wanted_version): upgrade = True if upgrade: os.system(f"{sys.executable} -m pip install -U 'venvstarter=={wanted_version}' -t {deps_dir}") manager = runpy.run_path(str(deps_dir / "venvstarter.py"))["manager"] dict2xml-1.7.5/tools/devtools.py0000644000000000000000000000635713615410400013556 0ustar00import inspect import os import platform import shlex import sys import typing as tp from pathlib import Path here = Path(__file__).parent if platform.system() == "Windows": import mslex # type:ignore[import] shlex = mslex # noqa if sys.version_info < (3, 10): Dict = tp.Dict List = tp.List else: Dict = dict List = list class Command: __is_command__: bool def __call__(self, bin_dir: Path, args: List[str]) -> None: ... def command(func: tp.Callable) -> tp.Callable: tp.cast(Command, func).__is_command__ = True return func def run(*args: tp.Union[str, Path]) -> None: cmd = " ".join(shlex.quote(str(part)) for part in args) print(f"Running '{cmd}'") ret = os.system(cmd) if ret != 0: sys.exit(1) class App: commands: Dict[str, Command] def __init__(self): self.commands = {} compare = inspect.signature(type("C", (Command,), {})().__call__) for name in dir(self): val = getattr(self, name) if getattr(val, "__is_command__", False): assert ( inspect.signature(val) == compare ), f"Expected '{name}' to have correct signature, have {inspect.signature(val)} instead of {compare}" self.commands[name] = val def __call__(self, args: List[str]) -> None: bin_dir = Path(sys.executable).parent if args and args[0] in self.commands: os.chdir(here.parent) self.commands[args[0]](bin_dir, args[1:]) return sys.exit(f"Unknown command:\nAvailable: {sorted(self.commands)}\nWanted: {args}") @command def format(self, bin_dir: Path, args: List[str]) -> None: if not args: args = [".", *args] run(bin_dir / "black", *args) run(bin_dir / "isort", *args) @command def lint(self, bin_dir: Path, args: List[str]) -> None: run(bin_dir / "pylama", *args) @command def tests(self, bin_dir: Path, args: List[str]) -> None: if "-q" not in args: args = ["-q", *args] env = os.environ env["NOSE_OF_YETI_BLACK_COMPAT"] = "false" files: list[str] = [] if "TESTS_CHDIR" in env: ags: list[str] = [] test_dir = Path(env["TESTS_CHDIR"]).absolute() for a in args: test_name = "" if "::" in a: filename, test_name = a.split("::", 1) else: filename = a try: p = Path(filename).absolute() except: ags.append(a) else: if p.exists(): rel = p.relative_to(test_dir) if test_name: files.append(f"{rel}::{test_name}") else: files.append(str(rel)) else: ags.append(a) args = ags os.chdir(test_dir) run(bin_dir / "pytest", *files, *args) @command def tox(self, bin_dir: Path, args: List[str]) -> None: run(bin_dir / "tox", *args) app = App() if __name__ == "__main__": app(sys.argv[1:]) dict2xml-1.7.5/tools/requirements.dev.txt0000644000000000000000000000031713615410400015374 0ustar00pylama-dmypy==0.4 pylama==8.4.1 neovim==0.3.1 tox==4.4.6 isort==5.11.5 python-lsp-server==1.7.1 pylsp-mypy==0.6.5 pyls-isort==0.2.2 python-lsp-black==1.2.1 hatch==1.6.3 mslex==0.3.0; sys.platform == 'win32' dict2xml-1.7.5/tools/venv0000755000000000000000000000174513615410400012245 0ustar00#!/usr/bin/env python3 import glob import os import runpy import shutil import subprocess import sys import typing as tp from pathlib import Path here = Path(__file__).parent manager = runpy.run_path(str(Path(__file__).parent / "bootstrap_venvstarter.py"))["manager"] def run(venv_location: Path, args: tp.List[str]) -> tp.Union[None, str, tp.List[str]]: devtools_location = Path(__file__).parent / "devtools.py" return ["python", str(devtools_location)] manager = manager(run).named(".python") manager.add_local_dep( "{here}", "..", version_file=("dict2xml", "version.py"), name="dict2xml=={version}", with_tests=True, ) if "TOX_PYTHON" in os.environ: folder = Path(os.environ["TOX_PYTHON"]).parent.parent manager.place_venv_in(folder.parent) manager.named(folder.name) else: manager.add_no_binary("black") manager.add_requirements_file("{here}", "requirements.dev.txt") manager.add_env(NOSE_OF_YETI_BLACK_COMPAT="true") manager.run() dict2xml-1.7.5/.gitignore0000644000000000000000000000005613615410400012163 0ustar00*.pyc build *.egg-info dist/ .tox .dmypy.json dict2xml-1.7.5/LICENSE0000644000000000000000000000207113615410400011177 0ustar00The 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. dict2xml-1.7.5/README.rst0000644000000000000000000001253413615410400011666 0ustar00dict2xml ======== Super Simple utility to convert a python dictionary into an xml string Installation ------------ Install using pip:: > python -m 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, iterables_repeat_wrap=True, closed_tags_for=None, data_sorter=None)`` Instance method on Converter that takes in the data and creates the xml string * iterables_repeat_wrap - when false the key the array is in will be repeated * closed_tags_for - an array of values that will produce self closing tags * data_sorter - an object as explained below for sorting keys in maps ``dict2xml.DataSorter`` An object used to determine the sorting of keys for a map of data. By default an ``OrderedDict`` object will not have it's keys sorted, but any other type of mapping will. It can be made so even ``OrderedDict`` will get sorted by passing in ``data_sorter=DataSorter.always()``. Or it can be made so that keys are produced from the sorting determined by the mapping with ``data_sorter=DataSorter.never()``. .. note:: When this library was first created python did not have deterministic sorting for normal dictionaries which is why default everything gets sorted but ``OrderedDict`` do not. To create custom sorting logic requires an object that has a single ``keys_from`` method on it that accepts a map of data and returns a list of strings, where only the keys that appear in the list will go into the output and those keys must exist in the original mapping. Self closing tags ----------------- To produce self closing tags (like ````) then the ``build`` method must be given a list of values under ``closed_tags_for``. For example, if you want ``None`` to produce a closing tag then: .. code-block:: python example = { "item1": None, "item2": {"string1": "", "string2": None}, "item3": "special", } result = Converter("").build(example, closed_tags_for=[None]) assert result == dedent(""" special """).strip()) Here only ``string2`` gets a self closing tag because it has data of ``None``, which has been designated as special. If you want to dynamically work out which tags should be self closing then you may provide an object that implements ``__eq__`` and do your logic there. 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.5 - 13 February 2024 * Introduced the ``data_sorter`` option 1.7.4 - 16 January 2024 * Make the tests compatible with pytest8 1.7.3 - 25 Feb 2023 * This version has no changes to the installed code. * This release converts to hatch for packaging and adds a wheel to the package on pypi. * CI will now run against python 3.11 as well 1.7.2 - 18 Oct 2022 * This version has no changes to the installed code. * This release adds the tests to the source distribution put onto pypi. 1.7.1 - 16 Feb 2022 * Adding an option to have self closing tags when the value for that tag equals certain values 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. Development ----------- To enter a virtualenv with dict2xml and dev requirements installed run:: > source run.sh activate Tests may be run with:: > ./test.sh Or:: > ./run.sh tox Linting and formatting is via:: > ./format > ./lint Python Black will work on the tests as long as ``NOSE_OF_YETI_BLACK_COMPAT=true`` and the correct version of black is available. This is true if your editor is opened in the same terminal session after sourcing run.sh or if you make sure that environment variable is set and the editor is using the virtualenv made by running or sourcing ``run.sh`` (``tools/venv/.python``) dict2xml-1.7.5/pyproject.toml0000644000000000000000000000264613615410400013116 0ustar00[build-system] requires = ["hatchling"] build-backend = "hatchling.build" [project] name = "dict2xml" dynamic = ["version"] description = "Small utility to convert a python dictionary into an XML string" readme = "README.rst" license = "MIT" requires-python = ">= 3.5" authors = [ { name = "Stephen Moore", email = "stephen@delfick.com" }, ] classifiers = [ "Development Status :: 5 - Production/Stable", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3", "Topic :: Software Development :: Libraries :: Python Modules", "Topic :: Text Processing :: Markup :: XML", ] [project.optional-dependencies] tests = [ "noseOfYeti[black]==2.4.4", "pytest==7.4.4", ] [project.urls] Homepage = "http://github.com/delfick/python-dict2xml" [tool.hatch.version] path = "dict2xml/version.py" [tool.hatch.build.targets.sdist] include = [ "/dict2xml", "/README.rst", "/LICENSE", "/test.sh", "/run.sh", "/pytest.ini", "/tests/**", "/tools/bootstrap_venvstarter.py", "/tools/requirements.dev.txt", "/tools/devtools.py", "/tools/venv" ] exclude = ["*.pyc"] [tool.black] line-length = 100 include = '\.py$' exclude = ''' /( \.git | \.tox | dist | tools )/ ''' [tool.isort] profile = "black" skip_glob = [ ".git/*", ".tox/*", "dist/*", "tools/.*", ] dict2xml-1.7.5/PKG-INFO0000644000000000000000000001425213615410400011273 0ustar00Metadata-Version: 2.1 Name: dict2xml Version: 1.7.5 Summary: Small utility to convert a python dictionary into an XML string Project-URL: Homepage, http://github.com/delfick/python-dict2xml Author-email: Stephen Moore License-Expression: MIT License-File: LICENSE 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 :: 3 Classifier: Topic :: Software Development :: Libraries :: Python Modules Classifier: Topic :: Text Processing :: Markup :: XML Requires-Python: >=3.5 Provides-Extra: tests Requires-Dist: noseofyeti[black]==2.4.4; extra == 'tests' Requires-Dist: pytest==7.4.4; extra == 'tests' Description-Content-Type: text/x-rst dict2xml ======== Super Simple utility to convert a python dictionary into an xml string Installation ------------ Install using pip:: > python -m 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, iterables_repeat_wrap=True, closed_tags_for=None, data_sorter=None)`` Instance method on Converter that takes in the data and creates the xml string * iterables_repeat_wrap - when false the key the array is in will be repeated * closed_tags_for - an array of values that will produce self closing tags * data_sorter - an object as explained below for sorting keys in maps ``dict2xml.DataSorter`` An object used to determine the sorting of keys for a map of data. By default an ``OrderedDict`` object will not have it's keys sorted, but any other type of mapping will. It can be made so even ``OrderedDict`` will get sorted by passing in ``data_sorter=DataSorter.always()``. Or it can be made so that keys are produced from the sorting determined by the mapping with ``data_sorter=DataSorter.never()``. .. note:: When this library was first created python did not have deterministic sorting for normal dictionaries which is why default everything gets sorted but ``OrderedDict`` do not. To create custom sorting logic requires an object that has a single ``keys_from`` method on it that accepts a map of data and returns a list of strings, where only the keys that appear in the list will go into the output and those keys must exist in the original mapping. Self closing tags ----------------- To produce self closing tags (like ````) then the ``build`` method must be given a list of values under ``closed_tags_for``. For example, if you want ``None`` to produce a closing tag then: .. code-block:: python example = { "item1": None, "item2": {"string1": "", "string2": None}, "item3": "special", } result = Converter("").build(example, closed_tags_for=[None]) assert result == dedent(""" special """).strip()) Here only ``string2`` gets a self closing tag because it has data of ``None``, which has been designated as special. If you want to dynamically work out which tags should be self closing then you may provide an object that implements ``__eq__`` and do your logic there. 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.5 - 13 February 2024 * Introduced the ``data_sorter`` option 1.7.4 - 16 January 2024 * Make the tests compatible with pytest8 1.7.3 - 25 Feb 2023 * This version has no changes to the installed code. * This release converts to hatch for packaging and adds a wheel to the package on pypi. * CI will now run against python 3.11 as well 1.7.2 - 18 Oct 2022 * This version has no changes to the installed code. * This release adds the tests to the source distribution put onto pypi. 1.7.1 - 16 Feb 2022 * Adding an option to have self closing tags when the value for that tag equals certain values 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. Development ----------- To enter a virtualenv with dict2xml and dev requirements installed run:: > source run.sh activate Tests may be run with:: > ./test.sh Or:: > ./run.sh tox Linting and formatting is via:: > ./format > ./lint Python Black will work on the tests as long as ``NOSE_OF_YETI_BLACK_COMPAT=true`` and the correct version of black is available. This is true if your editor is opened in the same terminal session after sourcing run.sh or if you make sure that environment variable is set and the editor is using the virtualenv made by running or sourcing ``run.sh`` (``tools/venv/.python``)