Added sample mock CMTS configuration.
[packetcable.git] / packetcable-client / xml2json
1 #!/usr/bin/env python
2
3 """xml2json.py  Convert XML to JSON
4
5 Relies on ElementTree for the XML parsing.  This is based on
6 pesterfish.py but uses a different XML->JSON mapping.
7 The XML->JSON mapping is described at
8 http://www.xml.com/pub/a/2006/05/31/converting-between-xml-and-json.html
9
10 Rewritten to a command line utility by Hay Kranen < github.com/hay > with
11 contributions from George Hamilton (gmh04) and Dan Brown (jdanbrown)
12
13 XML                              JSON
14 <e/>                             "e": null
15 <e>text</e>                      "e": "text"
16 <e name="value" />               "e": { "@name": "value" }
17 <e name="value">text</e>         "e": { "@name": "value", "#text": "text" }
18 <e> <a>text</a ><b>text</b> </e> "e": { "a": "text", "b": "text" }
19 <e> <a>text</a> <a>text</a> </e> "e": { "a": ["text", "text"] }
20 <e> text <a>text</a> </e>        "e": { "#text": "text", "a": "text" }
21
22 This is very similar to the mapping used for Yahoo Web Services
23 (http://developer.yahoo.com/common/json.html#xml).
24
25 This is a mess in that it is so unpredictable -- it requires lots of testing
26 (e.g. to see if values are lists or strings or dictionaries).  For use
27 in Python this could be vastly cleaner.  Think about whether the internal
28 form can be more self-consistent while maintaining good external
29 characteristics for the JSON.
30
31 Look at the Yahoo version closely to see how it works.  Maybe can adopt
32 that completely if it makes more sense...
33
34 R. White, 2006 November 6
35 """
36
37 import json
38 import optparse
39 import sys
40 import os
41
42 import xml.etree.cElementTree as ET
43
44
45 def strip_tag(tag):
46     strip_ns_tag = tag
47     split_array = tag.split('}')
48     if len(split_array) > 1:
49         strip_ns_tag = split_array[1]
50         tag = strip_ns_tag
51     return tag
52
53
54 def elem_to_internal(elem, strip_ns=1, strip=1):
55     """Convert an Element into an internal dictionary (not JSON!)."""
56
57     d = {}
58     elem_tag = elem.tag
59     if strip_ns:
60         elem_tag = strip_tag(elem.tag)
61     else:
62         for key, value in list(elem.attrib.items()):
63             d['@' + key] = value
64
65     # loop over subelements to merge them
66     for subelem in elem:
67         v = elem_to_internal(subelem, strip_ns=strip_ns, strip=strip)
68
69         tag = subelem.tag
70         if strip_ns:
71             tag = strip_tag(subelem.tag)
72
73         value = v[tag]
74
75         try:
76             # add to existing list for this tag
77             d[tag].append(value)
78         except AttributeError:
79             # turn existing entry into a list
80             d[tag] = [d[tag], value]
81         except KeyError:
82             # add a new non-list entry
83             d[tag] = value
84     text = elem.text
85     tail = elem.tail
86     if strip:
87         # ignore leading and trailing whitespace
88         if text:
89             text = text.strip()
90         if tail:
91             tail = tail.strip()
92
93     if tail:
94         d['#tail'] = tail
95
96     if d:
97         # use #text element if other attributes exist
98         if text:
99             d["#text"] = text
100     else:
101         # text is the value if no attributes
102         d = text or None
103     return {elem_tag: d}
104
105
106 def internal_to_elem(pfsh, factory=ET.Element):
107
108     """Convert an internal dictionary (not JSON!) into an Element.
109
110     Whatever Element implementation we could import will be
111     used by default; if you want to use something else, pass the
112     Element class as the factory parameter.
113     """
114
115     attribs = {}
116     text = None
117     tail = None
118     sublist = []
119     tag = list(pfsh.keys())
120     if len(tag) != 1:
121         raise ValueError("Illegal structure with multiple tags: %s" % tag)
122     tag = tag[0]
123     value = pfsh[tag]
124     if isinstance(value, dict):
125         for k, v in list(value.items()):
126             if k[:1] == "@":
127                 attribs[k[1:]] = v
128             elif k == "#text":
129                 text = v
130             elif k == "#tail":
131                 tail = v
132             elif isinstance(v, list):
133                 for v2 in v:
134                     sublist.append(internal_to_elem({k: v2}, factory=factory))
135             else:
136                 sublist.append(internal_to_elem({k: v}, factory=factory))
137     else:
138         text = value
139     e = factory(tag, attribs)
140     for sub in sublist:
141         e.append(sub)
142     e.text = text
143     e.tail = tail
144     return e
145
146
147 def elem2json(elem, options, strip_ns=1, strip=1):
148
149     """Convert an ElementTree or Element into a JSON string."""
150
151     if hasattr(elem, 'getroot'):
152         elem = elem.getroot()
153
154     if options.pretty:
155         return json.dumps(elem_to_internal(elem, strip_ns=strip_ns, strip=strip), sort_keys=True, indent=4, separators=(',', ': '))
156     else:
157         return json.dumps(elem_to_internal(elem, strip_ns=strip_ns, strip=strip))
158
159
160 def json2elem(json_data, factory=ET.Element):
161
162     """Convert a JSON string into an Element.
163
164     Whatever Element implementation we could import will be used by
165     default; if you want to use something else, pass the Element class
166     as the factory parameter.
167     """
168
169     return internal_to_elem(json.loads(json_data), factory)
170
171
172 def xml2json(xmlstring, options, strip_ns=1, strip=1):
173
174     """Convert an XML string into a JSON string."""
175
176     elem = ET.fromstring(xmlstring)
177     return elem2json(elem, options, strip_ns=strip_ns, strip=strip)
178
179
180 def json2xml(json_data, factory=ET.Element):
181
182     """Convert a JSON string into an XML string.
183
184     Whatever Element implementation we could import will be used by
185     default; if you want to use something else, pass the Element class
186     as the factory parameter.
187     """
188     if not isinstance(json_data, dict):
189         json_data = json.loads(json_data)
190
191     elem = internal_to_elem(json_data, factory)
192     return ET.tostring(elem)
193
194
195 def main():
196     p = optparse.OptionParser(
197         description='Converts XML to JSON or the other way around.  Reads from standard input by default, or from file if given.',
198         prog='xml2json',
199         usage='%prog -t xml2json -o file.json [file]'
200     )
201     p.add_option('--type', '-t', help="'xml2json' or 'json2xml'", default="xml2json")
202     p.add_option('--out', '-o', help="Write to OUT instead of stdout")
203     p.add_option(
204         '--strip_text', action="store_true",
205         dest="strip_text", help="Strip text for xml2json")
206     p.add_option(
207         '--pretty', action="store_true",
208         dest="pretty", help="Format JSON output so it is easier to read")
209     p.add_option(
210         '--strip_namespace', action="store_true",
211         dest="strip_ns", help="Strip namespace for xml2json")
212     p.add_option(
213         '--strip_newlines', action="store_true",
214         dest="strip_nl", help="Strip newlines for xml2json")
215     options, arguments = p.parse_args()
216
217     inputstream = sys.stdin
218     if len(arguments) == 1:
219         try:
220             inputstream = open(arguments[0])
221         except:
222             sys.stderr.write("Problem reading '{0}'\n".format(arguments[0]))
223             p.print_help()
224             sys.exit(-1)
225
226     input = inputstream.read()
227
228     strip = 0
229     strip_ns = 0
230     if options.strip_text:
231         strip = 1
232     if options.strip_ns:
233         strip_ns = 1
234     if options.strip_nl:
235         input = input.replace('\n', '').replace('\r','')
236     if (options.type == "xml2json"):
237         out = xml2json(input, options, strip_ns, strip)
238     else:
239         out = json2xml(input)
240
241     if (options.out):
242         file = open(options.out, 'w')
243         file.write(out)
244         file.close()
245     else:
246         print(out)
247
248 if __name__ == "__main__":
249     main()
250