~widelands-dev/widelands-website/django_staticfiles

« back to all changes in this revision

Viewing changes to pybb/lib/phpserialize.py

  • Committer: Holger Rapp
  • Date: 2009-02-21 18:24:02 UTC
  • Revision ID: sirver@kallisto.local-20090221182402-k3tuf5c4gjwslbjf
Main Page contains now the same informations as before

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# -*- coding: utf-8 -*-
2
 
r"""
3
 
    phpserialize
4
 
    ~~~~~~~~~~~~
5
 
 
6
 
    a port of the ``serialize`` and ``unserialize`` functions of
7
 
    php to python.  This module implements the python serialization
8
 
    interface (eg: provides `dumps`, `loads` and similar functions).
9
 
 
10
 
    Usage
11
 
    =====
12
 
 
13
 
    >>> from phpserialize import *
14
 
    >>> obj = dumps("Hello World")
15
 
    >>> loads(obj)
16
 
    'Hello World'
17
 
 
18
 
    Due to the fact that PHP doesn't know the concept of lists, lists
19
 
    are serialized like hash-maps in PHP.  As a matter of fact the
20
 
    reverse value of a serialized list is a dict:
21
 
 
22
 
    >>> loads(dumps(range(2)))
23
 
    {0: 0, 1: 1}
24
 
 
25
 
    If you want to have a list again, you can use the `dict_to_list`
26
 
    helper function:
27
 
 
28
 
    >>> dict_to_list(loads(dumps(range(2))))
29
 
    [0, 1]
30
 
 
31
 
    It's also possible to convert into a tuple by using the `dict_to_tuple`
32
 
    function:
33
 
 
34
 
    >>> dict_to_tuple(loads(dumps((1, 2, 3))))
35
 
    (1, 2, 3)
36
 
 
37
 
    Another problem are unicode strings.  By default unicode strings are
38
 
    encoded to 'utf-8' but not decoded on `unserialize`.  The reason for
39
 
    this is that phpserialize can't guess if you have binary or text data
40
 
    in the strings:
41
 
 
42
 
    >>> loads(dumps(u'Hello W\xf6rld'))
43
 
    'Hello W\xc3\xb6rld'
44
 
 
45
 
    If you know that you have only text data of a known charset in the result
46
 
    you can decode strings by setting `decode_strings` to True when calling
47
 
    loads:
48
 
 
49
 
    >>> loads(dumps(u'Hello W\xf6rld'), decode_strings=True)
50
 
    u'Hello W\xf6rld'
51
 
 
52
 
    Dictionary keys are limited to strings and integers.  `None` is converted
53
 
    into an empty string and floats and booleans into integers for PHP
54
 
    compatibility:
55
 
 
56
 
    >>> loads(dumps({None: 14, 42.23: 'foo', True: [1, 2, 3]}))
57
 
    {'': 14, 1: {0: 1, 1: 2, 2: 3}, 42: 'foo'}
58
 
 
59
 
    It also provides functions to read from file-like objects:
60
 
 
61
 
    >>> from StringIO import StringIO
62
 
    >>> stream = StringIO('a:2:{i:0;i:1;i:1;i:2;}')
63
 
    >>> dict_to_list(load(stream))
64
 
    [1, 2]
65
 
 
66
 
    And to write to those:
67
 
 
68
 
    >>> stream = StringIO()
69
 
    >>> dump([1, 2], stream)
70
 
    >>> stream.getvalue()
71
 
    'a:2:{i:0;i:1;i:1;i:2;}'
72
 
 
73
 
    Like `pickle` chaining of objects is supported:
74
 
 
75
 
    >>> stream = StringIO()
76
 
    >>> dump([1, 2], stream)
77
 
    >>> dump("foo", stream)
78
 
    >>> stream.seek(0)
79
 
    >>> load(stream)
80
 
    {0: 1, 1: 2}
81
 
    >>> load(stream)
82
 
    'foo'
83
 
 
84
 
    This feature however is not supported in PHP.  PHP will only unserialize
85
 
    the first object.
86
 
 
87
 
    CHANGELOG
88
 
    =========
89
 
 
90
 
    1.1
91
 
        -   added `dict_to_list` and `dict_to_tuple`
92
 
        -   added support for unicode
93
 
        -   allowed chaining of objects like pickle does.
94
 
 
95
 
 
96
 
    :copyright: 2007-2008 by Armin Ronacher.
97
 
    license: BSD
98
 
"""
99
 
from StringIO import StringIO
100
 
 
101
 
__author__ = 'Armin Ronacher <armin.ronacher@active-4.com>'
102
 
__version__ = '1.1'
103
 
 
104
 
 
105
 
def dumps(data, charset='utf-8', errors='strict'):
106
 
    """Return the PHP-serialized representation of the object as a string,
107
 
    instead of writing it to a file like `dump` does.
108
 
    """
109
 
    def _serialize(obj, keypos):
110
 
        if keypos:
111
 
            if isinstance(obj, (int, long, float, bool)):
112
 
                return 'i:%i;' % obj
113
 
            if isinstance(obj, basestring):
114
 
                if isinstance(obj, unicode):
115
 
                    obj = obj.encode(charset, errors)
116
 
                return 's:%i:"%s";' % (len(obj), obj)
117
 
            if obj is None:
118
 
                return 's:0:"";'
119
 
            raise TypeError('can\'t serialize %r as key' % type(obj))
120
 
        else:
121
 
            if obj is None:
122
 
                return 'N;'
123
 
            if isinstance(obj, bool):
124
 
                return 'b:%i;' % obj
125
 
            if isinstance(obj, (int, long)):
126
 
                return 'i:%s;' % obj
127
 
            if isinstance(obj, float):
128
 
                return 'd:%s;' % obj
129
 
            if isinstance(obj, basestring):
130
 
                if isinstance(obj, unicode):
131
 
                    obj = obj.encode(charset, errors)
132
 
                return 's:%i:"%s";' % (len(obj), obj)
133
 
            if isinstance(obj, (list, tuple, dict)):
134
 
                out = []
135
 
                if isinstance(obj, dict):
136
 
                    iterable = obj.iteritems()
137
 
                else:
138
 
                    iterable = enumerate(obj)
139
 
                for key, value in iterable:
140
 
                    out.append(_serialize(key, True))
141
 
                    out.append(_serialize(value, False))
142
 
                return 'a:%i:{%s}' % (len(obj), ''.join(out))
143
 
            raise TypeError('can\'t serialize %r' % type(obj))
144
 
    return _serialize(data, False)
145
 
 
146
 
 
147
 
def load(fp, charset='utf-8', errors='strict', decode_strings=False):
148
 
    """Read a string from the open file object `fp` and interpret it as a
149
 
    data stream of PHP-serialized objects, reconstructing and returning
150
 
    the original object hierarchy.
151
 
 
152
 
    `fp` must provide a `read()` method that takes an integer argument.  Both
153
 
    method should return strings.  Thus `fp` can be a file object opened for
154
 
    reading, a `StringIO` object, or any other custom object that meets this
155
 
    interface.
156
 
 
157
 
    `load` will read exactly one object from the stream.  See the docstring of
158
 
    the module for this chained behavior.
159
 
    """
160
 
    def _expect(e):
161
 
        v = fp.read(len(e))
162
 
        if v != e:
163
 
            raise ValueError('failed expectation, expected %r got %r' % (e, v))
164
 
 
165
 
    def _read_until(delim):
166
 
        buf = []
167
 
        while 1:
168
 
            char = fp.read(1)
169
 
            if char == delim:
170
 
                break
171
 
            elif not char:
172
 
                raise ValueError('unexpected end of stream')
173
 
            buf.append(char)
174
 
        return ''.join(buf)
175
 
 
176
 
    def _unserialize():
177
 
        type_ = fp.read(1).lower()
178
 
        if type_ == 'n':
179
 
            _expect(';')
180
 
            return None
181
 
        if type_ in 'idb':
182
 
            _expect(':')
183
 
            data = _read_until(';')
184
 
            if type_ == 'i':
185
 
                return int(data)
186
 
            if type_ == 'd':
187
 
                return float(data)
188
 
            return int(data) != 0
189
 
        if type_ == 's':
190
 
            _expect(':')
191
 
            length = int(_read_until(':'))
192
 
            _expect('"')
193
 
            data = fp.read(length)
194
 
            _expect('"')
195
 
            if decode_strings:
196
 
                data = data.decode(charset, errors)
197
 
            _expect(';')
198
 
            return data
199
 
        if type_ == 'a':
200
 
            _expect(':')
201
 
            items = int(_read_until(':')) * 2
202
 
            _expect('{')
203
 
            result = {}
204
 
            last_item = Ellipsis
205
 
            for idx in xrange(items):
206
 
                item = _unserialize()
207
 
                if last_item is Ellipsis:
208
 
                    last_item = item
209
 
                else:
210
 
                    result[last_item] = item
211
 
                    last_item = Ellipsis
212
 
            _expect('}')
213
 
            return result
214
 
        raise ValueError('unexpected opcode')
215
 
 
216
 
    return _unserialize()
217
 
 
218
 
 
219
 
def loads(data, charset='utf-8', errors='strict', decode_strings=False):
220
 
    """Read a PHP-serialized object hierarchy from a string.  Characters in the
221
 
    string past the object's representation are ignored.
222
 
    """
223
 
    return load(StringIO(data), charset, errors, decode_strings)
224
 
 
225
 
 
226
 
def dump(data, fp, charset='utf-8', errors='strict'):
227
 
    """Write a PHP-serialized representation of obj to the open file object
228
 
    `fp`.  Unicode strings are encoded to `charset` with the error handling
229
 
    of `errors`.
230
 
 
231
 
    `fp` must have a `write()` method that accepts a single string argument.
232
 
    It can thus be a file object opened for writing, a `StringIO` object, or
233
 
    any other custom object that meets this interface.
234
 
    """
235
 
    fp.write(dumps(data, charset, errors))
236
 
 
237
 
 
238
 
def dict_to_list(d):
239
 
    """Converts an ordered dict into a list."""
240
 
    try:
241
 
        return [d[x] for x in xrange(len(d))]
242
 
    except KeyError:
243
 
        raise ValueError('dict is not a sequence')
244
 
 
245
 
 
246
 
def dict_to_tuple(d):
247
 
    """Converts an ordered dict into a tuple."""
248
 
    return tuple(dict_to_list(d))
249
 
 
250
 
 
251
 
serialize = dumps
252
 
unserialize = loads
253
 
 
254
 
 
255
 
if __name__ == '__main__':
256
 
    import doctest
257
 
    doctest.testmod()