gchecky.tools

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