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 """ 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
80 - def _reduce_list(self, l):
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
91 - def _reduce_diction(self, diction):
92 # None value 93 if len(diction) == 0: 94 return None 95 96 # Strings, booleans and None values 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 # Lists 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 # elif data '_' 113 diction = data 114 return diction
115 116 @classmethod
117 - def _decode_tag(clazz, tag):
118 if len(tag) > 1 and tag[0:2] == '__': 119 return tag[2:] 120 return tag
121
122 - def _decode_into_dict(self, node):
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 #TODO !! 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
140 - def _add_to_dict(self, diction, key, data):
141 if key not in diction: 142 diction[key] = [data] 143 else: 144 # if not isinstance(diction[key], list): 145 # diction[key] = [diction[key]] 146 diction[key].append(data)
147 148 @classmethod
149 - def _decode_string(clazz, str):
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
191 -class encoder:
192 - def serialize(self, data, xml_node):
193 self.__doc = xml_node.ownerDocument 194 self.__markers = {} 195 self._encode(data=data, node=xml_node)
196 197 @classmethod
198 - def _encode_tag(clazz, tag):
199 return '__' + tag
200
201 - def _create_element(self, tag):
202 # TODO Account for wierd characters 203 return self.__doc.createElement(tag)
204
205 - def _create_text(self, value):
206 return self.__doc.createTextNode(value)
207 208 @classmethod
209 - def _escape_string(clazz, str):
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 # Great - the string won't be confused with int, long, 219 # float or boolean - just spit it out then. 220 return str 221 # Ok, do the safe escaping of the string value 222 return '"' + str.replace('"', '\\"') + '"'
223
224 - def _encode(self, data, node):
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
285 - def __mark(self, obj):
286 if id(obj) in self.__markers: 287 raise ValueError('gchecky.encoder can\'t handle cyclic references.') 288 self.__markers[id(obj)] = obj
289
290 - def __unmark(self, obj):
291 del self.__markers[id(obj)]
292 293 if __name__ == "__main__":
294 - def run_doctests():
295 import doctest 296 doctest.testmod()
297 run_doctests() 298