суббота, 20 февраля 2016 г.

Postfix marshaling format

Implementation of simple marshaling of nested data structures. Supports: int, float, string, nested lists. Serialized data is presented as string. It's not difficult to serialize into binary...

# POSTPACK -- packing the data in postfix notation (nested typed items)
#
# Format: item '!' type,
#   where type may be 'i' (int), 'f' (float), 's' (string), 'l' (list),
#   num 'l' (list with num items)
#
# Example: 1!i2!i!l3!i4!i!2l => [[1, 2], [3, 4]]
#
# To escape '!' '!' is used: 'Hello!!' => 'Hello!'

from __future__ import print_function
import string

DIGITS = string.digits


class Postpack:
    def __init__(self, fmt={}):
        self.fmt = {int: "%d", float: "%f", str: "%s"}
        self.fmt.update(fmt)

    def decode(self, input):
        ''' Decodes input string into internal representation (list of items,
        possible nested):

        >>> Postpack().decode("Hello, world!!!!!s2.1!f!2l3!i4!i!2l5!i!2l")
        [['Hello, world!!', 2.1], [[3, 4], 5]]
        '''
        buf = ""        # item chars buffer
        instr = ""      # instruction chars buffer
        items = []      # items
        st = "data"     # FSM state
        for ch in input:
            if st == "instr":
                if ch in DIGITS:
                    instr += ch
                elif ch == '!':
                    buf += ch
                    st = "data"
                else:
                    if ch == 'i':
                        items.append(int(buf))
                    elif ch == 'f':
                        items.append(float(buf))
                    elif ch == 's':
                        items.append(str(buf))
                    elif ch == 'l':
                        nitems = len(items)
                        count = int(instr) if instr else nitems
                        first = nitems - count
                        if first < 0:
                            raise ValueError("Wrong count: %d" % count)
                        items[first:] = [items[first:]]
                    else:
                        raise ValueError("Wrong instruction: %s" % ch)
                    st = "data"
                    buf = ""
            elif st == "data":
                if ch == "!":
                    instr = ""
                    st = "instr"
                else:
                    buf += ch
        return items


    @staticmethod
    def escape(s):
        '''
        >>> Postpack().escape('Qwe!rty!!')
        'Qwe!!rty!!!!'
        '''
        return s.replace('!', '!!')

    def encode(self, items):
        ''' Encodes list of items (may be nested):

        >>> Postpack(fmt={float: '%.1f'}).encode([['Hello, world!!', 2.1], [[3, 4], 5]])
        'Hello, world!!!!!s2.1!f!2l3!i4!i!2l5!i!2l'
        '''
        output = ""
        for it in items:
            ittype = type(it)
            if ittype is int:
                output += self.fmt[int] % it + "!i"
            elif ittype is float:
                output += self.fmt[float] % it + "!f"
            elif ittype is str:
                output += self.fmt[str] % self.escape(it) + "!s"
            elif ittype is list:
                output += self.encode(it) + "!%dl" % len(it)
            else:
                raise ValueError("Wrong type: %s" % ittype)
        return output




##############################################################################
if __name__ == "__main__":
    import doctest
    doctest.testmod()

Комментариев нет:

Отправить комментарий

Thanks for your posting!