1 """
2 The module provides two classes encoder and decoder that allow to
3 serialize and deserialize python-ish PODs into/from XML.
4 Note that data should be simple:
5 None, True, False, strings, lists, tupls, dicts
6 Anything other than this will trigger an error.
7
8 Also note that any circular references in the data will also trigger an error,
9 so please do not try to serialize something like:
10 >>> a = []
11 >>> a.append(a)
12 >>> a
13 [[...]]
14
15 Important notes:
16 - tuples are treated as lists and deserialized into lists.
17 - any empty list, tuple or dictionary is deserialized into None.
18
19 TODO: Expand the notes on how exactly the data values are serialized.
20
21 Some doctests:
22
23 >>> def test_enc_dec(data, return_res=False):
24 ... from xml.dom.minidom import parseString
25 ... doc = parseString('<?xml version="1.0"?><dummy/>')
26 ... encoder().serialize(data, doc.documentElement)
27 ... xml = doc.toprettyxml(' ')
28 ... data2 = decoder().deserialize(doc.documentElement)
29 ... if data2 != data:
30 ... msg = '''--- Expected: ---
31 ... %s
32 ... --- Got: ---
33 ... %s
34 ... === Xml: ===
35 ... %s
36 ... ''' % (data, data2, xml)
37 ... if return_res:
38 ... return data2
39 ... print msg
40
41 >>> test_enc_dec(None)
42 >>> test_enc_dec(True)
43 >>> test_enc_dec(False)
44 >>> test_enc_dec('string')
45 >>> test_enc_dec(u'string')
46 >>> test_enc_dec({'a':'b'})
47 >>> test_enc_dec([1,2])
48 >>> test_enc_dec(['1',2])
49 >>> test_enc_dec([1])
50 >>> test_enc_dec({'':'aa'})
51 >>> test_enc_dec(['_'])
52 >>> test_enc_dec(['aa',['bb','cc'],[None], None, ['_']])
53 >>> test_enc_dec([[False]])
54 >>> test_enc_dec([[False], None])
55 >>> test_enc_dec([False, True, [False], [[True]], [None]])
56 >>> test_enc_dec({'vasya':['aa', 'bb']})
57 >>> test_enc_dec({'name':['Peter', 'Mary'], 'age':[11, 15]})
58
59 To fix:
60 >>> test_enc_dec([], return_res=True) != None
61 False
62 >>> test_enc_dec({}, return_res=True) != None
63 False
64 """
65
66 TRUE_LABEL = u'True'
67 FALSE_LABEL = u'False'
68
71 """
72 >>> from xml.dom.minidom import parseString
73 >>> doc = parseString('<?xml version="1.0"?><merchant-private-data><I><want/>to</I><spend>a<month or="two"/>at<Maui/>!!</spend></merchant-private-data>')
74 >>> decoder().deserialize(doc.documentElement)
75 {u'I': {None: [u'to'], u'want': None}, u'spend': {None: [u'a', u'at', u'!!'], u'Maui': None, u'month': {u'or': u'two'}}}
76 """
77 data = self._decode_into_dict(node)
78 return data
79
81 if not isinstance(l, list):
82 return l
83 if len(l) == 0:
84 return l
85 if len(l) == 1:
86 return l[0]
87 if l[-1] is None:
88 return l[:-1]
89 return l
90
92
93 if len(diction) == 0:
94 return None
95
96
97 if len(diction) == 1 and None in diction:
98 if len(diction[None]) == 1:
99 return diction[None][0]
100 return diction[None]
101
102
103 if len(diction) == 1 and '_' in diction:
104 return self._reduce_list(diction['_'])
105
106 data = {}
107 for key in diction.keys():
108 if key is None:
109 data[None] = diction[None]
110 else:
111 data[decoder._decode_tag(key)] = self._reduce_list(diction[key])
112
113 diction = data
114 return diction
115
116 @classmethod
118 if len(tag) > 1 and tag[0:2] == '__':
119 return tag[2:]
120 return tag
121
123 diction = {None:[]}
124 for child in node.childNodes:
125 if child.nodeType is child.TEXT_NODE or child.nodeType == child.CDATA_SECTION_NODE:
126 diction[None].append(decoder._decode_string(child.data))
127 elif node.nodeType is child.ELEMENT_NODE:
128 data = self._decode_into_dict(child)
129 self._add_to_dict(diction, child.tagName, data)
130 else:
131
132 pass
133 for attr in node.attributes.keys():
134 data = decoder._decode_string(node.attributes[attr].nodeValue)
135 self._add_to_dict(diction, attr, data)
136 if len(diction[None]) == 0:
137 del diction[None]
138 return self._reduce_diction(diction)
139
141 if key not in diction:
142 diction[key] = [data]
143 else:
144
145
146 diction[key].append(data)
147
148 @classmethod
150 """
151 >>> decoder._decode_string(None)
152 >>> decoder._decode_string('True')
153 True
154 >>> decoder._decode_string('False')
155 False
156 >>> decoder._decode_string('11')
157 11
158 >>> decoder._decode_string('12L')
159 12L
160 >>> decoder._decode_string('11.')
161 11.0
162 >>> decoder._decode_string('some')
163 u'some'
164 >>> decoder._decode_string('"some"')
165 u'"some"'
166 >>> decoder._decode_string('"some')
167 u'"some'
168 """
169 if str is None:
170 return None
171 elif str == TRUE_LABEL:
172 return True
173 elif str == FALSE_LABEL:
174 return False
175 try:
176 return int(str)
177 except Exception:pass
178 try:
179 return long(str)
180 except Exception:pass
181 try:
182 return float(str)
183 except Exception:pass
184 str = unicode(str)
185 if str[0] == '"' and str[-1] == '"':
186 original = (str.replace('\\"', '"'))[1:-1]
187 if encoder._escape_string(original) == str:
188 return original
189 return unicode(str)
190
193 self.__doc = xml_node.ownerDocument
194 self.__markers = {}
195 self._encode(data=data, node=xml_node)
196
197 @classmethod
200
202
203 return self.__doc.createElement(tag)
204
205 - def _create_text(self, value):
206 return self.__doc.createTextNode(value)
207
208 @classmethod
210 if str.find('"') < 0:
211 if str != TRUE_LABEL and str != FALSE_LABEL:
212 try: int(str)
213 except:
214 try: long(str)
215 except:
216 try: float(str)
217 except:
218
219
220 return str
221
222 return '"' + str.replace('"', '\\"') + '"'
223
225 """
226 @param node Is either a string or an XML node. If its a string then
227 a node with such a name should be created, otherwise
228 the existing xml node should be populated.
229 """
230 if isinstance(data, (list, tuple)):
231 self.__mark(data)
232 children = []
233 if isinstance(node, basestring):
234 tag = encoder._encode_tag(node)
235 parent = None
236 else:
237 tag = '_'
238 parent = node
239
240 l = list(data)
241 if len(l) >= 1:
242 l.append(None)
243 for d in l:
244 child = self._create_element(tag)
245 if parent is not None:
246 parent.appendChild(child)
247 self._encode(d, child)
248 children.append(child)
249
250 return children
251 else:
252 if isinstance(node, basestring):
253 parent = self._create_element(encoder._encode_tag(node))
254 else:
255 parent = node
256
257 if isinstance(data, dict):
258 self.__mark(data)
259 for key in data.keys():
260 children = self._encode(data[key], key)
261 if isinstance(children, list):
262 for child in children:
263 parent.appendChild(child)
264 else:
265 parent.appendChild(children)
266 self.__unmark(data)
267 else:
268 if isinstance(data, basestring):
269 child = self._create_text(encoder._escape_string(unicode(data)))
270 elif data is None:
271 child = None
272 elif isinstance(data, (int, long, float)):
273 child = self._create_text(unicode(data))
274 elif data is True:
275 child = self._create_text(TRUE_LABEL)
276 elif data is False:
277 child = self._create_text(FALSE_LABEL)
278 else:
279 raise ValueError('Serialisation of "%s" is not supported.' % (data.__class__,))
280
281 if child is not None:
282 parent.appendChild(child)
283 return [parent]
284
286 if id(obj) in self.__markers:
287 raise ValueError('gchecky.encoder can\'t handle cyclic references.')
288 self.__markers[id(obj)] = obj
289
291 del self.__markers[id(obj)]
292
293 if __name__ == "__main__":
295 import doctest
296 doctest.testmod()
297 run_doctests()
298