1ef3e4c2b4d5f07acc33c66d01063fcdf00b7b7d9Benjamin Petersonr"""plistlib.py -- a tool to generate and parse MacOSX .plist files.
27e182548faeabc526f6eed546933e9745949f584Christian Heimes
36e9b1df499ef87bf8b21787219fbccee8c0075d7Ezio MelottiThe property list (.plist) file format is a simple XML pickle supporting
47e182548faeabc526f6eed546933e9745949f584Christian Heimesbasic object types, like dictionaries, lists, numbers and strings.
57e182548faeabc526f6eed546933e9745949f584Christian HeimesUsually the top level object is a dictionary.
67e182548faeabc526f6eed546933e9745949f584Christian Heimes
7c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald OussorenTo write out a plist file, use the dump(value, file)
8c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussorenfunction. 'value' is the top level object, 'file' is
9c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussorena (writable) file object.
107e182548faeabc526f6eed546933e9745949f584Christian Heimes
11c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald OussorenTo parse a plist from a file, use the load(file) function,
12c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussorenwith a (readable) file object as the only argument. It
137e182548faeabc526f6eed546933e9745949f584Christian Heimesreturns the top level object (again, usually a dictionary).
147e182548faeabc526f6eed546933e9745949f584Christian Heimes
15c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald OussorenTo work with plist data in bytes objects, you can use loads()
16c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussorenand dumps().
177e182548faeabc526f6eed546933e9745949f584Christian Heimes
187e182548faeabc526f6eed546933e9745949f584Christian HeimesValues can be strings, integers, floats, booleans, tuples, lists,
19c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussorendictionaries (but only with string keys), Data, bytes, bytearray, or
20c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussorendatetime.datetime objects.
217e182548faeabc526f6eed546933e9745949f584Christian Heimes
227e182548faeabc526f6eed546933e9745949f584Christian HeimesGenerate Plist example:
237e182548faeabc526f6eed546933e9745949f584Christian Heimes
247e182548faeabc526f6eed546933e9745949f584Christian Heimes    pl = dict(
256e9b1df499ef87bf8b21787219fbccee8c0075d7Ezio Melotti        aString = "Doodah",
266e9b1df499ef87bf8b21787219fbccee8c0075d7Ezio Melotti        aList = ["A", "B", 12, 32.1, [1, 2, 3]],
277e182548faeabc526f6eed546933e9745949f584Christian Heimes        aFloat = 0.1,
287e182548faeabc526f6eed546933e9745949f584Christian Heimes        anInt = 728,
296e9b1df499ef87bf8b21787219fbccee8c0075d7Ezio Melotti        aDict = dict(
306e9b1df499ef87bf8b21787219fbccee8c0075d7Ezio Melotti            anotherString = "<hello & hi there!>",
316e9b1df499ef87bf8b21787219fbccee8c0075d7Ezio Melotti            aUnicodeValue = "M\xe4ssig, Ma\xdf",
326e9b1df499ef87bf8b21787219fbccee8c0075d7Ezio Melotti            aTrueValue = True,
336e9b1df499ef87bf8b21787219fbccee8c0075d7Ezio Melotti            aFalseValue = False,
347e182548faeabc526f6eed546933e9745949f584Christian Heimes        ),
35c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        someData = b"<binary gunk>",
36c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        someMoreData = b"<lots of binary gunk>" * 10,
377e182548faeabc526f6eed546933e9745949f584Christian Heimes        aDate = datetime.datetime.fromtimestamp(time.mktime(time.gmtime())),
387e182548faeabc526f6eed546933e9745949f584Christian Heimes    )
39c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    with open(fileName, 'wb') as fp:
40c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        dump(pl, fp)
417e182548faeabc526f6eed546933e9745949f584Christian Heimes
427e182548faeabc526f6eed546933e9745949f584Christian HeimesParse Plist example:
437e182548faeabc526f6eed546933e9745949f584Christian Heimes
44c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    with open(fileName, 'rb') as fp:
45c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        pl = load(fp)
46c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    print(pl["aKey"])
477e182548faeabc526f6eed546933e9745949f584Christian Heimes"""
487e182548faeabc526f6eed546933e9745949f584Christian Heimes__all__ = [
497e182548faeabc526f6eed546933e9745949f584Christian Heimes    "readPlist", "writePlist", "readPlistFromBytes", "writePlistToBytes",
50d04d21373f01ff0d4b8aabfb73ea19b2c023f4a0Martin Panter    "Plist", "Data", "Dict", "InvalidFileException", "FMT_XML", "FMT_BINARY",
51c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    "load", "dump", "loads", "dumps"
527e182548faeabc526f6eed546933e9745949f584Christian Heimes]
537e182548faeabc526f6eed546933e9745949f584Christian Heimes
547e182548faeabc526f6eed546933e9745949f584Christian Heimesimport binascii
55c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussorenimport codecs
56c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussorenimport contextlib
577e182548faeabc526f6eed546933e9745949f584Christian Heimesimport datetime
58c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussorenimport enum
597e182548faeabc526f6eed546933e9745949f584Christian Heimesfrom io import BytesIO
60c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussorenimport itertools
61c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussorenimport os
627e182548faeabc526f6eed546933e9745949f584Christian Heimesimport re
63c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussorenimport struct
64c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussorenfrom warnings import warn
65c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussorenfrom xml.parsers.expat import ParserCreate
667e182548faeabc526f6eed546933e9745949f584Christian Heimes
677e182548faeabc526f6eed546933e9745949f584Christian Heimes
68c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald OussorenPlistFormat = enum.Enum('PlistFormat', 'FMT_XML FMT_BINARY', module=__name__)
69c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussorenglobals().update(PlistFormat.__members__)
707e182548faeabc526f6eed546933e9745949f584Christian Heimes
717e182548faeabc526f6eed546933e9745949f584Christian Heimes
72c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren#
73c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren#
74c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren# Deprecated functionality
75c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren#
76c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren#
777e182548faeabc526f6eed546933e9745949f584Christian Heimes
787e182548faeabc526f6eed546933e9745949f584Christian Heimes
797e182548faeabc526f6eed546933e9745949f584Christian Heimesclass _InternalDict(dict):
807e182548faeabc526f6eed546933e9745949f584Christian Heimes
817e182548faeabc526f6eed546933e9745949f584Christian Heimes    # This class is needed while Dict is scheduled for deprecation:
827e182548faeabc526f6eed546933e9745949f584Christian Heimes    # we only need to warn when a *user* instantiates Dict or when
837e182548faeabc526f6eed546933e9745949f584Christian Heimes    # the "attribute notation for dict keys" is used.
84c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    __slots__ = ()
857e182548faeabc526f6eed546933e9745949f584Christian Heimes
867e182548faeabc526f6eed546933e9745949f584Christian Heimes    def __getattr__(self, attr):
877e182548faeabc526f6eed546933e9745949f584Christian Heimes        try:
887e182548faeabc526f6eed546933e9745949f584Christian Heimes            value = self[attr]
897e182548faeabc526f6eed546933e9745949f584Christian Heimes        except KeyError:
907e182548faeabc526f6eed546933e9745949f584Christian Heimes            raise AttributeError(attr)
917e182548faeabc526f6eed546933e9745949f584Christian Heimes        warn("Attribute access from plist dicts is deprecated, use d[key] "
92b575289292902bb078dbd128a90da642c27d4ad6Victor Stinner             "notation instead", DeprecationWarning, 2)
937e182548faeabc526f6eed546933e9745949f584Christian Heimes        return value
947e182548faeabc526f6eed546933e9745949f584Christian Heimes
957e182548faeabc526f6eed546933e9745949f584Christian Heimes    def __setattr__(self, attr, value):
967e182548faeabc526f6eed546933e9745949f584Christian Heimes        warn("Attribute access from plist dicts is deprecated, use d[key] "
97b575289292902bb078dbd128a90da642c27d4ad6Victor Stinner             "notation instead", DeprecationWarning, 2)
987e182548faeabc526f6eed546933e9745949f584Christian Heimes        self[attr] = value
997e182548faeabc526f6eed546933e9745949f584Christian Heimes
1007e182548faeabc526f6eed546933e9745949f584Christian Heimes    def __delattr__(self, attr):
1017e182548faeabc526f6eed546933e9745949f584Christian Heimes        try:
1027e182548faeabc526f6eed546933e9745949f584Christian Heimes            del self[attr]
1037e182548faeabc526f6eed546933e9745949f584Christian Heimes        except KeyError:
1047e182548faeabc526f6eed546933e9745949f584Christian Heimes            raise AttributeError(attr)
1057e182548faeabc526f6eed546933e9745949f584Christian Heimes        warn("Attribute access from plist dicts is deprecated, use d[key] "
106b575289292902bb078dbd128a90da642c27d4ad6Victor Stinner             "notation instead", DeprecationWarning, 2)
1077e182548faeabc526f6eed546933e9745949f584Christian Heimes
108c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
1097e182548faeabc526f6eed546933e9745949f584Christian Heimesclass Dict(_InternalDict):
1107e182548faeabc526f6eed546933e9745949f584Christian Heimes
1117e182548faeabc526f6eed546933e9745949f584Christian Heimes    def __init__(self, **kwargs):
1127e182548faeabc526f6eed546933e9745949f584Christian Heimes        warn("The plistlib.Dict class is deprecated, use builtin dict instead",
113b575289292902bb078dbd128a90da642c27d4ad6Victor Stinner             DeprecationWarning, 2)
1147e182548faeabc526f6eed546933e9745949f584Christian Heimes        super().__init__(**kwargs)
1157e182548faeabc526f6eed546933e9745949f584Christian Heimes
1167e182548faeabc526f6eed546933e9745949f584Christian Heimes
117c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren@contextlib.contextmanager
118c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussorendef _maybe_open(pathOrFile, mode):
119c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    if isinstance(pathOrFile, str):
120c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        with open(pathOrFile, mode) as fp:
121c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            yield fp
122c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
123c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    else:
124c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        yield pathOrFile
125c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
1267e182548faeabc526f6eed546933e9745949f584Christian Heimes
127c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussorenclass Plist(_InternalDict):
128c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    """This class has been deprecated. Use dump() and load()
1297e182548faeabc526f6eed546933e9745949f584Christian Heimes    functions instead, together with regular dict objects.
1307e182548faeabc526f6eed546933e9745949f584Christian Heimes    """
1317e182548faeabc526f6eed546933e9745949f584Christian Heimes
1327e182548faeabc526f6eed546933e9745949f584Christian Heimes    def __init__(self, **kwargs):
133c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        warn("The Plist class is deprecated, use the load() and "
134c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren             "dump() functions instead", DeprecationWarning, 2)
1357e182548faeabc526f6eed546933e9745949f584Christian Heimes        super().__init__(**kwargs)
1367e182548faeabc526f6eed546933e9745949f584Christian Heimes
137c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    @classmethod
1387e182548faeabc526f6eed546933e9745949f584Christian Heimes    def fromFile(cls, pathOrFile):
139c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        """Deprecated. Use the load() function instead."""
140c06d6fdc37a70c28feaac60b06047b9f6357c05fNed Deily        with _maybe_open(pathOrFile, 'rb') as fp:
141c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            value = load(fp)
1427e182548faeabc526f6eed546933e9745949f584Christian Heimes        plist = cls()
143c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        plist.update(value)
1447e182548faeabc526f6eed546933e9745949f584Christian Heimes        return plist
1457e182548faeabc526f6eed546933e9745949f584Christian Heimes
1467e182548faeabc526f6eed546933e9745949f584Christian Heimes    def write(self, pathOrFile):
147c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        """Deprecated. Use the dump() function instead."""
148c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        with _maybe_open(pathOrFile, 'wb') as fp:
149c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            dump(self, fp)
1507e182548faeabc526f6eed546933e9745949f584Christian Heimes
1517e182548faeabc526f6eed546933e9745949f584Christian Heimes
152c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussorendef readPlist(pathOrFile):
153c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    """
154c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    Read a .plist from a path or file. pathOrFile should either
155c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    be a file name, or a readable binary file object.
156c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
157c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    This function is deprecated, use load instead.
158c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    """
159c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    warn("The readPlist function is deprecated, use load() instead",
160c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        DeprecationWarning, 2)
161c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
162c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    with _maybe_open(pathOrFile, 'rb') as fp:
163c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        return load(fp, fmt=None, use_builtin_types=False,
164c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            dict_type=_InternalDict)
165c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
166c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussorendef writePlist(value, pathOrFile):
167c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    """
168c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    Write 'value' to a .plist file. 'pathOrFile' may either be a
169c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    file name or a (writable) file object.
170c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
171c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    This function is deprecated, use dump instead.
172c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    """
173c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    warn("The writePlist function is deprecated, use dump() instead",
174c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        DeprecationWarning, 2)
175c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    with _maybe_open(pathOrFile, 'wb') as fp:
176c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        dump(value, fp, fmt=FMT_XML, sort_keys=True, skipkeys=False)
177c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
178c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
179c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussorendef readPlistFromBytes(data):
180c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    """
181c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    Read a plist data from a bytes object. Return the root object.
182c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
183c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    This function is deprecated, use loads instead.
184c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    """
185c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    warn("The readPlistFromBytes function is deprecated, use loads() instead",
186c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        DeprecationWarning, 2)
187c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    return load(BytesIO(data), fmt=None, use_builtin_types=False,
188c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        dict_type=_InternalDict)
189c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
190c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
191c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussorendef writePlistToBytes(value):
192c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    """
193c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    Return 'value' as a plist-formatted bytes object.
194c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
195c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    This function is deprecated, use dumps instead.
196c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    """
197c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    warn("The writePlistToBytes function is deprecated, use dumps() instead",
198c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        DeprecationWarning, 2)
199c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    f = BytesIO()
200c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    dump(value, f, fmt=FMT_XML, sort_keys=True, skipkeys=False)
201c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    return f.getvalue()
202c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
2037e182548faeabc526f6eed546933e9745949f584Christian Heimes
2047e182548faeabc526f6eed546933e9745949f584Christian Heimesclass Data:
205c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    """
206c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    Wrapper for binary data.
2077e182548faeabc526f6eed546933e9745949f584Christian Heimes
208c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    This class is deprecated, use a bytes object instead.
209c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    """
2107e182548faeabc526f6eed546933e9745949f584Christian Heimes
2117e182548faeabc526f6eed546933e9745949f584Christian Heimes    def __init__(self, data):
2127e182548faeabc526f6eed546933e9745949f584Christian Heimes        if not isinstance(data, bytes):
2137e182548faeabc526f6eed546933e9745949f584Christian Heimes            raise TypeError("data must be as bytes")
2147e182548faeabc526f6eed546933e9745949f584Christian Heimes        self.data = data
2157e182548faeabc526f6eed546933e9745949f584Christian Heimes
2167e182548faeabc526f6eed546933e9745949f584Christian Heimes    @classmethod
2177e182548faeabc526f6eed546933e9745949f584Christian Heimes    def fromBase64(cls, data):
218706824f19f734e5e567f32d989376547c0ae08daGeorg Brandl        # base64.decodebytes just calls binascii.a2b_base64;
2197e182548faeabc526f6eed546933e9745949f584Christian Heimes        # it seems overkill to use both base64 and binascii.
220c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        return cls(_decode_base64(data))
2217e182548faeabc526f6eed546933e9745949f584Christian Heimes
2227e182548faeabc526f6eed546933e9745949f584Christian Heimes    def asBase64(self, maxlinelength=76):
223c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        return _encode_base64(self.data, maxlinelength)
2247e182548faeabc526f6eed546933e9745949f584Christian Heimes
2257e182548faeabc526f6eed546933e9745949f584Christian Heimes    def __eq__(self, other):
2267e182548faeabc526f6eed546933e9745949f584Christian Heimes        if isinstance(other, self.__class__):
2277e182548faeabc526f6eed546933e9745949f584Christian Heimes            return self.data == other.data
228dd1bcdf6187ff18fb1536a6190926b0f03336186Serhiy Storchaka        elif isinstance(other, bytes):
2297e182548faeabc526f6eed546933e9745949f584Christian Heimes            return self.data == other
2307e182548faeabc526f6eed546933e9745949f584Christian Heimes        else:
231dd1bcdf6187ff18fb1536a6190926b0f03336186Serhiy Storchaka            return NotImplemented
2327e182548faeabc526f6eed546933e9745949f584Christian Heimes
2337e182548faeabc526f6eed546933e9745949f584Christian Heimes    def __repr__(self):
2347e182548faeabc526f6eed546933e9745949f584Christian Heimes        return "%s(%s)" % (self.__class__.__name__, repr(self.data))
2357e182548faeabc526f6eed546933e9745949f584Christian Heimes
236c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren#
237c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren#
238c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren# End of deprecated functionality
239c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren#
240c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren#
241c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
242c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
243c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren#
244c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren# XML support
245c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren#
246c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
247c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
248c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren# XML 'header'
249c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald OussorenPLISTHEADER = b"""\
250c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren<?xml version="1.0" encoding="UTF-8"?>
251c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
252c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren"""
253c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
254c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
255c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren# Regex to find any control chars, except for \t \n and \r
256c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren_controlCharPat = re.compile(
257c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    r"[\x00\x01\x02\x03\x04\x05\x06\x07\x08\x0b\x0c\x0e\x0f"
258c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    r"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f]")
259c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
260c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussorendef _encode_base64(s, maxlinelength=76):
261c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    # copied from base64.encodebytes(), with added maxlinelength argument
262c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    maxbinsize = (maxlinelength//4)*3
263c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    pieces = []
264c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    for i in range(0, len(s), maxbinsize):
265c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        chunk = s[i : i + maxbinsize]
266c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        pieces.append(binascii.b2a_base64(chunk))
267c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    return b''.join(pieces)
268c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
269c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussorendef _decode_base64(s):
270c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    if isinstance(s, str):
271c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        return binascii.a2b_base64(s.encode("utf-8"))
272c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
273c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    else:
274c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        return binascii.a2b_base64(s)
2757e182548faeabc526f6eed546933e9745949f584Christian Heimes
276c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren# Contents should conform to a subset of ISO 8601
277c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren# (in particular, YYYY '-' MM '-' DD 'T' HH ':' MM ':' SS 'Z'.  Smaller units
278c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren# may be omitted with #  a loss of precision)
279c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren_dateParser = re.compile(r"(?P<year>\d\d\d\d)(?:-(?P<month>\d\d)(?:-(?P<day>\d\d)(?:T(?P<hour>\d\d)(?::(?P<minute>\d\d)(?::(?P<second>\d\d))?)?)?)?)?Z", re.ASCII)
280c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
281c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
282c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussorendef _date_from_string(s):
283c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    order = ('year', 'month', 'day', 'hour', 'minute', 'second')
284c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    gd = _dateParser.match(s).groupdict()
285c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    lst = []
286c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    for key in order:
287c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        val = gd[key]
288c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        if val is None:
289c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            break
290c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        lst.append(int(val))
291c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    return datetime.datetime(*lst)
292c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
293c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
294c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussorendef _date_to_string(d):
295c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    return '%04d-%02d-%02dT%02d:%02d:%02dZ' % (
296c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        d.year, d.month, d.day,
297c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        d.hour, d.minute, d.second
298c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    )
299c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
300c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussorendef _escape(text):
301c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    m = _controlCharPat.search(text)
302c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    if m is not None:
303c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        raise ValueError("strings can't contains control characters; "
304c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren                         "use bytes instead")
305c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    text = text.replace("\r\n", "\n")       # convert DOS line endings
306c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    text = text.replace("\r", "\n")         # convert Mac line endings
307c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    text = text.replace("&", "&amp;")       # escape '&'
308c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    text = text.replace("<", "&lt;")        # escape '<'
309c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    text = text.replace(">", "&gt;")        # escape '>'
310c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    return text
311c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
312c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussorenclass _PlistParser:
313c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    def __init__(self, use_builtin_types, dict_type):
3147e182548faeabc526f6eed546933e9745949f584Christian Heimes        self.stack = []
315c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        self.current_key = None
3167e182548faeabc526f6eed546933e9745949f584Christian Heimes        self.root = None
317c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        self._use_builtin_types = use_builtin_types
318c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        self._dict_type = dict_type
3197e182548faeabc526f6eed546933e9745949f584Christian Heimes
3207e182548faeabc526f6eed546933e9745949f584Christian Heimes    def parse(self, fileobj):
321b8e59f77e65ba4caeda8d910bd66df01a468cbeaNed Deily        self.parser = ParserCreate()
322c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        self.parser.StartElementHandler = self.handle_begin_element
323c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        self.parser.EndElementHandler = self.handle_end_element
324c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        self.parser.CharacterDataHandler = self.handle_data
325b8e59f77e65ba4caeda8d910bd66df01a468cbeaNed Deily        self.parser.ParseFile(fileobj)
3267e182548faeabc526f6eed546933e9745949f584Christian Heimes        return self.root
3277e182548faeabc526f6eed546933e9745949f584Christian Heimes
328c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    def handle_begin_element(self, element, attrs):
3297e182548faeabc526f6eed546933e9745949f584Christian Heimes        self.data = []
3307e182548faeabc526f6eed546933e9745949f584Christian Heimes        handler = getattr(self, "begin_" + element, None)
3317e182548faeabc526f6eed546933e9745949f584Christian Heimes        if handler is not None:
3327e182548faeabc526f6eed546933e9745949f584Christian Heimes            handler(attrs)
3337e182548faeabc526f6eed546933e9745949f584Christian Heimes
334c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    def handle_end_element(self, element):
3357e182548faeabc526f6eed546933e9745949f584Christian Heimes        handler = getattr(self, "end_" + element, None)
3367e182548faeabc526f6eed546933e9745949f584Christian Heimes        if handler is not None:
3377e182548faeabc526f6eed546933e9745949f584Christian Heimes            handler()
3387e182548faeabc526f6eed546933e9745949f584Christian Heimes
339c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    def handle_data(self, data):
3407e182548faeabc526f6eed546933e9745949f584Christian Heimes        self.data.append(data)
3417e182548faeabc526f6eed546933e9745949f584Christian Heimes
342c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    def add_object(self, value):
343c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        if self.current_key is not None:
344b8e59f77e65ba4caeda8d910bd66df01a468cbeaNed Deily            if not isinstance(self.stack[-1], type({})):
345b8e59f77e65ba4caeda8d910bd66df01a468cbeaNed Deily                raise ValueError("unexpected element at line %d" %
346b8e59f77e65ba4caeda8d910bd66df01a468cbeaNed Deily                                 self.parser.CurrentLineNumber)
347c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            self.stack[-1][self.current_key] = value
348c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            self.current_key = None
3497e182548faeabc526f6eed546933e9745949f584Christian Heimes        elif not self.stack:
3507e182548faeabc526f6eed546933e9745949f584Christian Heimes            # this is the root object
3517e182548faeabc526f6eed546933e9745949f584Christian Heimes            self.root = value
3527e182548faeabc526f6eed546933e9745949f584Christian Heimes        else:
353b8e59f77e65ba4caeda8d910bd66df01a468cbeaNed Deily            if not isinstance(self.stack[-1], type([])):
354b8e59f77e65ba4caeda8d910bd66df01a468cbeaNed Deily                raise ValueError("unexpected element at line %d" %
355b8e59f77e65ba4caeda8d910bd66df01a468cbeaNed Deily                                 self.parser.CurrentLineNumber)
3567e182548faeabc526f6eed546933e9745949f584Christian Heimes            self.stack[-1].append(value)
3577e182548faeabc526f6eed546933e9745949f584Christian Heimes
358c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    def get_data(self):
3597e182548faeabc526f6eed546933e9745949f584Christian Heimes        data = ''.join(self.data)
3607e182548faeabc526f6eed546933e9745949f584Christian Heimes        self.data = []
3617e182548faeabc526f6eed546933e9745949f584Christian Heimes        return data
3627e182548faeabc526f6eed546933e9745949f584Christian Heimes
3637e182548faeabc526f6eed546933e9745949f584Christian Heimes    # element handlers
3647e182548faeabc526f6eed546933e9745949f584Christian Heimes
3657e182548faeabc526f6eed546933e9745949f584Christian Heimes    def begin_dict(self, attrs):
366c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        d = self._dict_type()
367c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        self.add_object(d)
3687e182548faeabc526f6eed546933e9745949f584Christian Heimes        self.stack.append(d)
369c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
3707e182548faeabc526f6eed546933e9745949f584Christian Heimes    def end_dict(self):
371c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        if self.current_key:
372b8e59f77e65ba4caeda8d910bd66df01a468cbeaNed Deily            raise ValueError("missing value for key '%s' at line %d" %
373c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren                             (self.current_key,self.parser.CurrentLineNumber))
3747e182548faeabc526f6eed546933e9745949f584Christian Heimes        self.stack.pop()
3757e182548faeabc526f6eed546933e9745949f584Christian Heimes
3767e182548faeabc526f6eed546933e9745949f584Christian Heimes    def end_key(self):
377c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        if self.current_key or not isinstance(self.stack[-1], type({})):
378b8e59f77e65ba4caeda8d910bd66df01a468cbeaNed Deily            raise ValueError("unexpected key at line %d" %
379b8e59f77e65ba4caeda8d910bd66df01a468cbeaNed Deily                             self.parser.CurrentLineNumber)
380c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        self.current_key = self.get_data()
3817e182548faeabc526f6eed546933e9745949f584Christian Heimes
3827e182548faeabc526f6eed546933e9745949f584Christian Heimes    def begin_array(self, attrs):
3837e182548faeabc526f6eed546933e9745949f584Christian Heimes        a = []
384c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        self.add_object(a)
3857e182548faeabc526f6eed546933e9745949f584Christian Heimes        self.stack.append(a)
386c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
3877e182548faeabc526f6eed546933e9745949f584Christian Heimes    def end_array(self):
3887e182548faeabc526f6eed546933e9745949f584Christian Heimes        self.stack.pop()
3897e182548faeabc526f6eed546933e9745949f584Christian Heimes
3907e182548faeabc526f6eed546933e9745949f584Christian Heimes    def end_true(self):
391c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        self.add_object(True)
392c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
3937e182548faeabc526f6eed546933e9745949f584Christian Heimes    def end_false(self):
394c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        self.add_object(False)
395c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
3967e182548faeabc526f6eed546933e9745949f584Christian Heimes    def end_integer(self):
397c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        self.add_object(int(self.get_data()))
398c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
3997e182548faeabc526f6eed546933e9745949f584Christian Heimes    def end_real(self):
400c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        self.add_object(float(self.get_data()))
401c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
4027e182548faeabc526f6eed546933e9745949f584Christian Heimes    def end_string(self):
403c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        self.add_object(self.get_data())
404c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
4057e182548faeabc526f6eed546933e9745949f584Christian Heimes    def end_data(self):
406c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        if self._use_builtin_types:
407c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            self.add_object(_decode_base64(self.get_data()))
408c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
409c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        else:
410c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            self.add_object(Data.fromBase64(self.get_data()))
411c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
4127e182548faeabc526f6eed546933e9745949f584Christian Heimes    def end_date(self):
413c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        self.add_object(_date_from_string(self.get_data()))
414c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
415c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
416c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussorenclass _DumbXMLWriter:
417c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    def __init__(self, file, indent_level=0, indent="\t"):
418c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        self.file = file
419c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        self.stack = []
420c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        self._indent_level = indent_level
421c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        self.indent = indent
422c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
423c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    def begin_element(self, element):
424c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        self.stack.append(element)
425c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        self.writeln("<%s>" % element)
426c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        self._indent_level += 1
427c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
428c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    def end_element(self, element):
429c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        assert self._indent_level > 0
430c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        assert self.stack.pop() == element
431c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        self._indent_level -= 1
432c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        self.writeln("</%s>" % element)
433c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
434c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    def simple_element(self, element, value=None):
435c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        if value is not None:
436c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            value = _escape(value)
437c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            self.writeln("<%s>%s</%s>" % (element, value, element))
438c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
439c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        else:
440c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            self.writeln("<%s/>" % element)
441c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
442c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    def writeln(self, line):
443c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        if line:
444c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            # plist has fixed encoding of utf-8
445c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
446c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            # XXX: is this test needed?
447c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            if isinstance(line, str):
448c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren                line = line.encode('utf-8')
449c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            self.file.write(self._indent_level * self.indent)
450c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            self.file.write(line)
451c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        self.file.write(b'\n')
452c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
453c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
454c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussorenclass _PlistWriter(_DumbXMLWriter):
455c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    def __init__(
456c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            self, file, indent_level=0, indent=b"\t", writeHeader=1,
457c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            sort_keys=True, skipkeys=False):
458c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
459c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        if writeHeader:
460c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            file.write(PLISTHEADER)
461c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        _DumbXMLWriter.__init__(self, file, indent_level, indent)
462c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        self._sort_keys = sort_keys
463c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        self._skipkeys = skipkeys
464c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
465c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    def write(self, value):
466c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        self.writeln("<plist version=\"1.0\">")
467c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        self.write_value(value)
468c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        self.writeln("</plist>")
469c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
470c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    def write_value(self, value):
471c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        if isinstance(value, str):
472c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            self.simple_element("string", value)
473c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
474c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        elif value is True:
475c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            self.simple_element("true")
476c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
477c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        elif value is False:
478c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            self.simple_element("false")
479c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
480c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        elif isinstance(value, int):
4816db6653bbc0841600248e4d7b7542591067c4157Ronald Oussoren            if -1 << 63 <= value < 1 << 64:
4826db6653bbc0841600248e4d7b7542591067c4157Ronald Oussoren                self.simple_element("integer", "%d" % value)
4836db6653bbc0841600248e4d7b7542591067c4157Ronald Oussoren            else:
4846db6653bbc0841600248e4d7b7542591067c4157Ronald Oussoren                raise OverflowError(value)
485c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
486c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        elif isinstance(value, float):
487c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            self.simple_element("real", repr(value))
488c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
489c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        elif isinstance(value, dict):
490c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            self.write_dict(value)
491c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
492c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        elif isinstance(value, Data):
493c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            self.write_data(value)
494c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
495c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        elif isinstance(value, (bytes, bytearray)):
496c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            self.write_bytes(value)
497c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
498c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        elif isinstance(value, datetime.datetime):
499c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            self.simple_element("date", _date_to_string(value))
500c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
501c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        elif isinstance(value, (tuple, list)):
502c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            self.write_array(value)
503c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
504c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        else:
505c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            raise TypeError("unsupported type: %s" % type(value))
506c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
507c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    def write_data(self, data):
508c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        self.write_bytes(data.data)
509c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
510c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    def write_bytes(self, data):
511c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        self.begin_element("data")
512c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        self._indent_level -= 1
513c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        maxlinelength = max(
514c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            16,
515c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            76 - len(self.indent.replace(b"\t", b" " * 8) * self._indent_level))
516c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
517c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        for line in _encode_base64(data, maxlinelength).split(b"\n"):
518c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            if line:
519c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren                self.writeln(line)
520c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        self._indent_level += 1
521c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        self.end_element("data")
522c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
523c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    def write_dict(self, d):
524c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        if d:
525c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            self.begin_element("dict")
526c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            if self._sort_keys:
527c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren                items = sorted(d.items())
528c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            else:
529c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren                items = d.items()
530c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
531c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            for key, value in items:
532c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren                if not isinstance(key, str):
533c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren                    if self._skipkeys:
534c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren                        continue
535c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren                    raise TypeError("keys must be strings")
536c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren                self.simple_element("key", key)
537c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren                self.write_value(value)
538c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            self.end_element("dict")
539c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
540c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        else:
541c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            self.simple_element("dict")
542c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
543c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    def write_array(self, array):
544c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        if array:
545c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            self.begin_element("array")
546c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            for value in array:
547c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren                self.write_value(value)
548c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            self.end_element("array")
549c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
550c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        else:
551c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            self.simple_element("array")
552c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
553c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
554c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussorendef _is_fmt_xml(header):
555c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    prefixes = (b'<?xml', b'<plist')
556c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
557c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    for pfx in prefixes:
558c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        if header.startswith(pfx):
559c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            return True
560c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
561c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    # Also check for alternative XML encodings, this is slightly
562c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    # overkill because the Apple tools (and plistlib) will not
563c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    # generate files with these encodings.
564c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    for bom, encoding in (
565c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren                (codecs.BOM_UTF8, "utf-8"),
566c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren                (codecs.BOM_UTF16_BE, "utf-16-be"),
567c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren                (codecs.BOM_UTF16_LE, "utf-16-le"),
568c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren                # expat does not support utf-32
569c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren                #(codecs.BOM_UTF32_BE, "utf-32-be"),
570c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren                #(codecs.BOM_UTF32_LE, "utf-32-le"),
571c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            ):
572c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        if not header.startswith(bom):
573c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            continue
574c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
575c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        for start in prefixes:
576c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            prefix = bom + start.decode('ascii').encode(encoding)
577c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            if header[:len(prefix)] == prefix:
578c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren                return True
579c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
580c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    return False
581c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
582c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren#
583c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren# Binary Plist
584c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren#
585c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
586c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
587c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussorenclass InvalidFileException (ValueError):
588c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    def __init__(self, message="Invalid file"):
589c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        ValueError.__init__(self, message)
590c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
591c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren_BINARY_FORMAT = {1: 'B', 2: 'H', 4: 'L', 8: 'Q'}
592c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
593c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussorenclass _BinaryPlistParser:
594c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    """
595c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    Read or write a binary plist file, following the description of the binary
596c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    format.  Raise InvalidFileException in case of error, otherwise return the
597c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    root object.
598c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
599c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    see also: http://opensource.apple.com/source/CF/CF-744.18/CFBinaryPList.c
600c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    """
601c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    def __init__(self, use_builtin_types, dict_type):
602c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        self._use_builtin_types = use_builtin_types
603c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        self._dict_type = dict_type
604c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
605c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    def parse(self, fp):
606c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        try:
607c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            # The basic file format:
608c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            # HEADER
609c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            # object...
610c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            # refid->offset...
611c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            # TRAILER
612c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            self._fp = fp
613c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            self._fp.seek(-32, os.SEEK_END)
614c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            trailer = self._fp.read(32)
615c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            if len(trailer) != 32:
616c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren                raise InvalidFileException()
617c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            (
618c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren                offset_size, self._ref_size, num_objects, top_object,
619c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren                offset_table_offset
620c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            ) = struct.unpack('>6xBBQQQ', trailer)
621c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            self._fp.seek(offset_table_offset)
622065266450ea5519a43bcc199e48d304f1e7038e8Serhiy Storchaka            self._object_offsets = self._read_ints(num_objects, offset_size)
623c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            return self._read_object(self._object_offsets[top_object])
624c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
625c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        except (OSError, IndexError, struct.error):
626c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            raise InvalidFileException()
627c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
628c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    def _get_size(self, tokenL):
629c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        """ return the size of the next object."""
630c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        if tokenL == 0xF:
631c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            m = self._fp.read(1)[0] & 0x3
632c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            s = 1 << m
633c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            f = '>' + _BINARY_FORMAT[s]
634c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            return struct.unpack(f, self._fp.read(s))[0]
635c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
636c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        return tokenL
637c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
638065266450ea5519a43bcc199e48d304f1e7038e8Serhiy Storchaka    def _read_ints(self, n, size):
639065266450ea5519a43bcc199e48d304f1e7038e8Serhiy Storchaka        data = self._fp.read(size * n)
640065266450ea5519a43bcc199e48d304f1e7038e8Serhiy Storchaka        if size in _BINARY_FORMAT:
641065266450ea5519a43bcc199e48d304f1e7038e8Serhiy Storchaka            return struct.unpack('>' + _BINARY_FORMAT[size] * n, data)
642065266450ea5519a43bcc199e48d304f1e7038e8Serhiy Storchaka        else:
643065266450ea5519a43bcc199e48d304f1e7038e8Serhiy Storchaka            return tuple(int.from_bytes(data[i: i + size], 'big')
644065266450ea5519a43bcc199e48d304f1e7038e8Serhiy Storchaka                         for i in range(0, size * n, size))
645065266450ea5519a43bcc199e48d304f1e7038e8Serhiy Storchaka
646c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    def _read_refs(self, n):
647065266450ea5519a43bcc199e48d304f1e7038e8Serhiy Storchaka        return self._read_ints(n, self._ref_size)
648c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
649c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    def _read_object(self, offset):
650c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        """
651c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        read the object at offset.
652c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
653c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        May recursively read sub-objects (content of an array/dict/set)
654c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        """
655c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        self._fp.seek(offset)
656c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        token = self._fp.read(1)[0]
657c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        tokenH, tokenL = token & 0xF0, token & 0x0F
658c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
659c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        if token == 0x00:
660c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            return None
661c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
662c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        elif token == 0x08:
663c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            return False
664c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
665c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        elif token == 0x09:
666c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            return True
667c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
668c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        # The referenced source code also mentions URL (0x0c, 0x0d) and
669c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        # UUID (0x0e), but neither can be generated using the Cocoa libraries.
670c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
671c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        elif token == 0x0f:
672c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            return b''
673c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
674c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        elif tokenH == 0x10:  # int
6756db6653bbc0841600248e4d7b7542591067c4157Ronald Oussoren            return int.from_bytes(self._fp.read(1 << tokenL),
6766db6653bbc0841600248e4d7b7542591067c4157Ronald Oussoren                                  'big', signed=tokenL >= 3)
677c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
678c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        elif token == 0x22: # real
679c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            return struct.unpack('>f', self._fp.read(4))[0]
680c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
681c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        elif token == 0x23: # real
682c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            return struct.unpack('>d', self._fp.read(8))[0]
683c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
684c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        elif token == 0x33:  # date
685c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            f = struct.unpack('>d', self._fp.read(8))[0]
686c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            # timestamp 0 of binary plists corresponds to 1/1/2001
687c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            # (year of Mac OS X 10.0), instead of 1/1/1970.
68894ad49fabc15c2eaafb5b590701ceb642d56bec0Serhiy Storchaka            return datetime.datetime(2001, 1, 1) + datetime.timedelta(seconds=f)
689c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
690c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        elif tokenH == 0x40:  # data
691c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            s = self._get_size(tokenL)
692c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            if self._use_builtin_types:
693c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren                return self._fp.read(s)
694c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            else:
695c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren                return Data(self._fp.read(s))
696c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
697c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        elif tokenH == 0x50:  # ascii string
698c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            s = self._get_size(tokenL)
699c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            result =  self._fp.read(s).decode('ascii')
700c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            return result
701c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
702c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        elif tokenH == 0x60:  # unicode string
703c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            s = self._get_size(tokenL)
704c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            return self._fp.read(s * 2).decode('utf-16be')
705c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
706c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        # tokenH == 0x80 is documented as 'UID' and appears to be used for
707c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        # keyed-archiving, not in plists.
708c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
709c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        elif tokenH == 0xA0:  # array
710c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            s = self._get_size(tokenL)
711c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            obj_refs = self._read_refs(s)
712c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            return [self._read_object(self._object_offsets[x])
713c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren                for x in obj_refs]
714c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
715c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        # tokenH == 0xB0 is documented as 'ordset', but is not actually
716c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        # implemented in the Apple reference code.
717c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
718c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        # tokenH == 0xC0 is documented as 'set', but sets cannot be used in
719c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        # plists.
720c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
721c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        elif tokenH == 0xD0:  # dict
722c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            s = self._get_size(tokenL)
723c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            key_refs = self._read_refs(s)
724c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            obj_refs = self._read_refs(s)
725c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            result = self._dict_type()
726c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            for k, o in zip(key_refs, obj_refs):
727c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren                result[self._read_object(self._object_offsets[k])
728c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren                    ] = self._read_object(self._object_offsets[o])
729c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            return result
730c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
731c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        raise InvalidFileException()
732c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
733c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussorendef _count_to_size(count):
734c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    if count < 1 << 8:
735c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        return 1
736c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
737c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    elif count < 1 << 16:
738c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        return 2
739c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
740c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    elif count << 1 << 32:
741c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        return 4
742c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
743c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    else:
744c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        return 8
745c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
746c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussorenclass _BinaryPlistWriter (object):
747c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    def __init__(self, fp, sort_keys, skipkeys):
748c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        self._fp = fp
749c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        self._sort_keys = sort_keys
750c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        self._skipkeys = skipkeys
751c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
752c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    def write(self, value):
753c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
754c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        # Flattened object list:
755c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        self._objlist = []
756c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
757c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        # Mappings from object->objectid
758c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        # First dict has (type(object), object) as the key,
759c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        # second dict is used when object is not hashable and
760c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        # has id(object) as the key.
761c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        self._objtable = {}
762c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        self._objidtable = {}
763c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
764c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        # Create list of all objects in the plist
765c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        self._flatten(value)
766c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
767c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        # Size of object references in serialized containers
768c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        # depends on the number of objects in the plist.
769c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        num_objects = len(self._objlist)
770c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        self._object_offsets = [0]*num_objects
771c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        self._ref_size = _count_to_size(num_objects)
772c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
773c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        self._ref_format = _BINARY_FORMAT[self._ref_size]
774c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
775c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        # Write file header
776c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        self._fp.write(b'bplist00')
777c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
778c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        # Write object list
779c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        for obj in self._objlist:
780c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            self._write_object(obj)
781c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
782c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        # Write refnum->object offset table
783c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        top_object = self._getrefnum(value)
784c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        offset_table_offset = self._fp.tell()
785c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        offset_size = _count_to_size(offset_table_offset)
786c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        offset_format = '>' + _BINARY_FORMAT[offset_size] * num_objects
787c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        self._fp.write(struct.pack(offset_format, *self._object_offsets))
788c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
789c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        # Write trailer
790c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        sort_version = 0
791c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        trailer = (
792c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            sort_version, offset_size, self._ref_size, num_objects,
793c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            top_object, offset_table_offset
794c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        )
795c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        self._fp.write(struct.pack('>5xBBBQQQ', *trailer))
796c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
797c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    def _flatten(self, value):
798c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        # First check if the object is in the object table, not used for
799c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        # containers to ensure that two subcontainers with the same contents
800c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        # will be serialized as distinct values.
801c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        if isinstance(value, (
802c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren                str, int, float, datetime.datetime, bytes, bytearray)):
803c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            if (type(value), value) in self._objtable:
804c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren                return
805c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
806c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        elif isinstance(value, Data):
807c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            if (type(value.data), value.data) in self._objtable:
808c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren                return
809c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
810c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        # Add to objectreference map
811c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        refnum = len(self._objlist)
812c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        self._objlist.append(value)
813c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        try:
814c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            if isinstance(value, Data):
815c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren                self._objtable[(type(value.data), value.data)] = refnum
816c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            else:
817c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren                self._objtable[(type(value), value)] = refnum
818c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        except TypeError:
819c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            self._objidtable[id(value)] = refnum
820c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
821c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        # And finally recurse into containers
822c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        if isinstance(value, dict):
823c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            keys = []
824c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            values = []
825c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            items = value.items()
826c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            if self._sort_keys:
827c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren                items = sorted(items)
828c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
829c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            for k, v in items:
830c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren                if not isinstance(k, str):
831c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren                    if self._skipkeys:
832c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren                        continue
833c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren                    raise TypeError("keys must be strings")
834c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren                keys.append(k)
835c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren                values.append(v)
836c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
837c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            for o in itertools.chain(keys, values):
838c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren                self._flatten(o)
839c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
840c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        elif isinstance(value, (list, tuple)):
841c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            for o in value:
842c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren                self._flatten(o)
843c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
844c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    def _getrefnum(self, value):
845c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        try:
846c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            if isinstance(value, Data):
847c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren                return self._objtable[(type(value.data), value.data)]
848c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            else:
849c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren                return self._objtable[(type(value), value)]
850c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        except TypeError:
851c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            return self._objidtable[id(value)]
852c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
853c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    def _write_size(self, token, size):
854c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        if size < 15:
855c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            self._fp.write(struct.pack('>B', token | size))
856c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
857c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        elif size < 1 << 8:
858c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            self._fp.write(struct.pack('>BBB', token | 0xF, 0x10, size))
859c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
860c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        elif size < 1 << 16:
861c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            self._fp.write(struct.pack('>BBH', token | 0xF, 0x11, size))
862c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
863c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        elif size < 1 << 32:
864c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            self._fp.write(struct.pack('>BBL', token | 0xF, 0x12, size))
865c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
866c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        else:
867c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            self._fp.write(struct.pack('>BBQ', token | 0xF, 0x13, size))
868c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
869c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    def _write_object(self, value):
870c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        ref = self._getrefnum(value)
871c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        self._object_offsets[ref] = self._fp.tell()
872c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        if value is None:
873c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            self._fp.write(b'\x00')
874c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
875c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        elif value is False:
876c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            self._fp.write(b'\x08')
877c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
878c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        elif value is True:
879c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            self._fp.write(b'\x09')
880c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
881c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        elif isinstance(value, int):
8826db6653bbc0841600248e4d7b7542591067c4157Ronald Oussoren            if value < 0:
8836db6653bbc0841600248e4d7b7542591067c4157Ronald Oussoren                try:
8846db6653bbc0841600248e4d7b7542591067c4157Ronald Oussoren                    self._fp.write(struct.pack('>Bq', 0x13, value))
8856db6653bbc0841600248e4d7b7542591067c4157Ronald Oussoren                except struct.error:
88694e44a935b3dc1e67a6a3357f64324ee0c81d40cRonald Oussoren                    raise OverflowError(value) from None
8876db6653bbc0841600248e4d7b7542591067c4157Ronald Oussoren            elif value < 1 << 8:
888c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren                self._fp.write(struct.pack('>BB', 0x10, value))
889c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            elif value < 1 << 16:
890c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren                self._fp.write(struct.pack('>BH', 0x11, value))
891c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            elif value < 1 << 32:
892c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren                self._fp.write(struct.pack('>BL', 0x12, value))
89394e44a935b3dc1e67a6a3357f64324ee0c81d40cRonald Oussoren            elif value < 1 << 63:
89494e44a935b3dc1e67a6a3357f64324ee0c81d40cRonald Oussoren                self._fp.write(struct.pack('>BQ', 0x13, value))
89594e44a935b3dc1e67a6a3357f64324ee0c81d40cRonald Oussoren            elif value < 1 << 64:
89694e44a935b3dc1e67a6a3357f64324ee0c81d40cRonald Oussoren                self._fp.write(b'\x14' + value.to_bytes(16, 'big', signed=True))
897c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            else:
89894e44a935b3dc1e67a6a3357f64324ee0c81d40cRonald Oussoren                raise OverflowError(value)
899c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
900c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        elif isinstance(value, float):
901c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            self._fp.write(struct.pack('>Bd', 0x23, value))
902c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
903c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        elif isinstance(value, datetime.datetime):
904c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            f = (value - datetime.datetime(2001, 1, 1)).total_seconds()
905c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            self._fp.write(struct.pack('>Bd', 0x33, f))
906c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
907c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        elif isinstance(value, Data):
908c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            self._write_size(0x40, len(value.data))
909c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            self._fp.write(value.data)
910c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
911c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        elif isinstance(value, (bytes, bytearray)):
912c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            self._write_size(0x40, len(value))
913c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            self._fp.write(value)
914c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
915c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        elif isinstance(value, str):
916c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            try:
917c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren                t = value.encode('ascii')
918c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren                self._write_size(0x50, len(value))
919c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            except UnicodeEncodeError:
920c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren                t = value.encode('utf-16be')
9217338ebc4ba00366fa2c4f592630c2acd98c178d8Serhiy Storchaka                self._write_size(0x60, len(t) // 2)
922c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
923c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            self._fp.write(t)
924c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
925c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        elif isinstance(value, (list, tuple)):
926c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            refs = [self._getrefnum(o) for o in value]
927c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            s = len(refs)
928c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            self._write_size(0xA0, s)
929c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            self._fp.write(struct.pack('>' + self._ref_format * s, *refs))
930c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
931c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        elif isinstance(value, dict):
932c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            keyRefs, valRefs = [], []
933c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
934c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            if self._sort_keys:
935c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren                rootItems = sorted(value.items())
936c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            else:
937c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren                rootItems = value.items()
938c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
939c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            for k, v in rootItems:
940c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren                if not isinstance(k, str):
941c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren                    if self._skipkeys:
942c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren                        continue
943c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren                    raise TypeError("keys must be strings")
944c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren                keyRefs.append(self._getrefnum(k))
945c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren                valRefs.append(self._getrefnum(v))
946c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
947c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            s = len(keyRefs)
948c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            self._write_size(0xD0, s)
949c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            self._fp.write(struct.pack('>' + self._ref_format * s, *keyRefs))
950c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            self._fp.write(struct.pack('>' + self._ref_format * s, *valRefs))
951c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
952c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        else:
9536db6653bbc0841600248e4d7b7542591067c4157Ronald Oussoren            raise TypeError(value)
954c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
955c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
956c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussorendef _is_fmt_binary(header):
957c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    return header[:8] == b'bplist00'
958c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
959c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
960c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren#
961c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren# Generic bits
962c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren#
963c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
964c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren_FORMATS={
965c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    FMT_XML: dict(
966c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        detect=_is_fmt_xml,
967c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        parser=_PlistParser,
968c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        writer=_PlistWriter,
969c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    ),
970c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    FMT_BINARY: dict(
971c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        detect=_is_fmt_binary,
972c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        parser=_BinaryPlistParser,
973c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        writer=_BinaryPlistWriter,
974c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    )
975c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren}
976c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
977c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
978c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussorendef load(fp, *, fmt=None, use_builtin_types=True, dict_type=dict):
979c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    """Read a .plist file. 'fp' should be (readable) file object.
980c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    Return the unpacked root object (which usually is a dictionary).
981c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    """
982c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    if fmt is None:
983c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        header = fp.read(32)
984c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        fp.seek(0)
985c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        for info in _FORMATS.values():
986c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            if info['detect'](header):
9878966759b031ce9977e038c5db1d8ed47c6c827a6Serhiy Storchaka                P = info['parser']
988c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren                break
989c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
990c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        else:
991c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren            raise InvalidFileException()
992c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
993c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    else:
9948966759b031ce9977e038c5db1d8ed47c6c827a6Serhiy Storchaka        P = _FORMATS[fmt]['parser']
995c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
9968966759b031ce9977e038c5db1d8ed47c6c827a6Serhiy Storchaka    p = P(use_builtin_types=use_builtin_types, dict_type=dict_type)
997c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    return p.parse(fp)
998c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
999c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
1000c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussorendef loads(value, *, fmt=None, use_builtin_types=True, dict_type=dict):
1001c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    """Read a .plist file from a bytes object.
1002c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    Return the unpacked root object (which usually is a dictionary).
1003c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    """
1004c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    fp = BytesIO(value)
1005c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    return load(
1006c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        fp, fmt=fmt, use_builtin_types=use_builtin_types, dict_type=dict_type)
1007c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
1008c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
1009c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussorendef dump(value, fp, *, fmt=FMT_XML, sort_keys=True, skipkeys=False):
1010c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    """Write 'value' to a .plist file. 'fp' should be a (writable)
1011c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    file object.
1012c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    """
1013c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    if fmt not in _FORMATS:
1014c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren        raise ValueError("Unsupported format: %r"%(fmt,))
1015c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
1016c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    writer = _FORMATS[fmt]["writer"](fp, sort_keys=sort_keys, skipkeys=skipkeys)
1017c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    writer.write(value)
1018c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
1019c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren
1020c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussorendef dumps(value, *, fmt=FMT_XML, skipkeys=False, sort_keys=True):
1021c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    """Return a bytes object with the contents for a .plist file.
1022c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    """
1023c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    fp = BytesIO()
1024c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    dump(value, fp, fmt=fmt, skipkeys=skipkeys, sort_keys=sort_keys)
1025c5cf7973422dce0ed59849aaf2d708d4c6b7320dRonald Oussoren    return fp.getvalue()
1026