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          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