~widelands-dev/widelands-website/add_DISPLAY_hint

« back to all changes in this revision

Viewing changes to pybb/lib/phpserialize.py

  • Committer: Holger Rapp
  • Date: 2009-02-25 16:55:36 UTC
  • Revision ID: sirver@kallisto.local-20090225165536-3abfhjx8qsgtzyru
- Added my hacked version of pybb. Remerging new versions is very difficult at this point :(

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