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
73
75 if not isinstance(l, list):
76 return l
77 if len(l) == 0:
78 return l
79 if len(l) == 1:
80 return l[0]
81 if l[-1] is None:
82 return l[:-1]
83 return l
84
86
87 if len(diction) == 0:
88 return None
89
90
91 if len(diction) == 1 and None in diction:
92 if len(diction[None]) == 1:
93 return diction[None][0]
94 return diction[None]
95
96
97 if len(diction) == 1 and '_' in diction:
98 return self._reduce_list(diction['_'])
99
100 data = {}
101 for key in diction.keys():
102 if key is None:
103 data[None] = diction[None]
104 else:
105 data[decoder._decode_tag(key)] = self._reduce_list(diction[key])
106
107 diction = data
108 return diction
109
110 @classmethod
112 if len(tag) > 1 and tag[0:2] == '__':
113 return tag[2:]
114 return tag
115
117 diction = {None:[]}
118 for child in node.childNodes:
119 if child.nodeType is child.TEXT_NODE or child.nodeType == child.CDATA_SECTION_NODE:
120 diction[None].append(decoder._decode_string(child.data))
121 elif node.nodeType is child.ELEMENT_NODE:
122 data = self._decode_into_dict(child)
123 self._add_to_dict(diction, child.tagName, data)
124 else:
125
126 pass
127 for attr in node.attributes.keys():
128 data = decoder._decode_string(node.attributes[attr])
129 self._add_to_dict(diction, attr, data)
130 if len(diction[None]) == 0:
131 del diction[None]
132 return self._reduce_diction(diction)
133
135 if key not in diction:
136 diction[key] = [data]
137 else:
138
139
140 diction[key].append(data)
141
142 @classmethod
144 """
145 >>> decoder._decode_string(None)
146 >>> decoder._decode_string('True')
147 True
148 >>> decoder._decode_string('False')
149 False
150 >>> decoder._decode_string('11')
151 11
152 >>> decoder._decode_string('12L')
153 12L
154 >>> decoder._decode_string('11.')
155 11.0
156 >>> decoder._decode_string('some')
157 u'some'
158 >>> decoder._decode_string('"some"')
159 u'"some"'
160 >>> decoder._decode_string('"some')
161 u'"some'
162 """
163 if str is None:
164 return None
165 elif str == TRUE_LABEL:
166 return True
167 elif str == FALSE_LABEL:
168 return False
169 try:
170 return int(str)
171 except ValueError:pass
172 try:
173 return long(str)
174 except ValueError:pass
175 try:
176 return float(str)
177 except ValueError:pass
178 str = unicode(str)
179 if str[0] == '"' and str[-1] == '"':
180 original = (str.replace('\\"', '"'))[1:-1]
181 if encoder._escape_string(original) == str:
182 return original
183 return unicode(str)
184
187 self.__doc = xml_node.ownerDocument
188 self.__markers = {}
189 self._encode(data=data, node=xml_node)
190
191 @classmethod
194
196
197 return self.__doc.createElement(tag)
198
199 - def _create_text(self, value):
200 return self.__doc.createTextNode(value)
201
202 @classmethod
204 if str.find('"') < 0:
205 if str != TRUE_LABEL and str != FALSE_LABEL:
206 try: int(str)
207 except:
208 try: long(str)
209 except:
210 try: float(str)
211 except:
212
213
214 return str
215
216 return '"' + str.replace('"', '\\"') + '"'
217
219 """
220 @param node Is either a string or an XML node. If its a string then
221 a node with such a name should be created, otherwise
222 the existing xml node should be populated.
223 """
224 if isinstance(data, (list, tuple)):
225 self.__mark(data)
226 children = []
227 if isinstance(node, basestring):
228 tag = encoder._encode_tag(node)
229 parent = None
230 else:
231 tag = '_'
232 parent = node
233
234 l = list(data)
235 if len(l) >= 1:
236 l.append(None)
237 for d in l:
238 child = self._create_element(tag)
239 if parent is not None:
240 parent.appendChild(child)
241 self._encode(d, child)
242 children.append(child)
243
244 return children
245 else:
246 if isinstance(node, basestring):
247 parent = self._create_element(encoder._encode_tag(node))
248 else:
249 parent = node
250
251 if isinstance(data, dict):
252 self.__mark(data)
253 for key in data.keys():
254 children = self._encode(data[key], key)
255 if isinstance(children, list):
256 for child in children:
257 parent.appendChild(child)
258 else:
259 parent.appendChild(children)
260 self.__unmark(data)
261 else:
262 if isinstance(data, basestring):
263 child = self._create_text(encoder._escape_string(unicode(data)))
264 elif data is None:
265 child = None
266 elif isinstance(data, (int, long, float)):
267 child = self._create_text(unicode(data))
268 elif data is True:
269 child = self._create_text(TRUE_LABEL)
270 elif data is False:
271 child = self._create_text(FALSE_LABEL)
272 else:
273 raise ValueError('Serialisation of "%s" is not supported.' % (data.__class__,))
274
275 if child is not None:
276 parent.appendChild(child)
277 return [parent]
278
280 if id(obj) in self.__markers:
281 raise ValueError('gchecky.encoder can\'t handle cyclic references.')
282 self.__markers[id(obj)] = obj
283
285 del self.__markers[id(obj)]
286
287 if __name__ == "__main__":
289 import doctest
290 doctest.testmod()
291 run_doctests()
292