Package gchecky :: Module tools
[hide private]
[frames] | no frames]

Source Code for Module gchecky.tools

  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   
69 -class decoder:
70 - def deserialize(self, node):
71 data = self._decode_into_dict(node) 72 return data
73
74 - def _reduce_list(self, l):
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
85 - def _reduce_diction(self, diction):
86 # None value 87 if len(diction) == 0: 88 return None 89 90 # Strings, booleans and None values 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 # Lists 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 # elif data '_' 107 diction = data 108 return diction
109 110 @classmethod
111 - def _decode_tag(clazz, tag):
112 if len(tag) > 1 and tag[0:2] == '__': 113 return tag[2:] 114 return tag
115
116 - def _decode_into_dict(self, node):
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 #TODO !! 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
134 - def _add_to_dict(self, diction, key, data):
135 if key not in diction: 136 diction[key] = [data] 137 else: 138 # if not isinstance(diction[key], list): 139 # diction[key] = [diction[key]] 140 diction[key].append(data)
141 142 @classmethod
143 - def _decode_string(clazz, str):
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
185 -class encoder:
186 - def serialize(self, data, xml_node):
187 self.__doc = xml_node.ownerDocument 188 self.__markers = {} 189 self._encode(data=data, node=xml_node)
190 191 @classmethod
192 - def _encode_tag(clazz, tag):
193 return '__' + tag
194
195 - def _create_element(self, tag):
196 # TODO Account for wierd characters 197 return self.__doc.createElement(tag)
198
199 - def _create_text(self, value):
200 return self.__doc.createTextNode(value)
201 202 @classmethod
203 - def _escape_string(clazz, str):
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 # Great - the string won't be confused with int, long, 213 # float or boolean - just spit it out then. 214 return str 215 # Ok, do the safe escaping of the string value 216 return '"' + str.replace('"', '\\"') + '"'
217
218 - def _encode(self, data, node):
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
279 - def __mark(self, obj):
280 if id(obj) in self.__markers: 281 raise ValueError('gchecky.encoder can\'t handle cyclic references.') 282 self.__markers[id(obj)] = obj
283
284 - def __unmark(self, obj):
285 del self.__markers[id(obj)]
286 287 if __name__ == "__main__":
288 - def run_doctests():
289 import doctest 290 doctest.testmod()
291 run_doctests() 292