Commit 0e837d2d authored by Ronald Oussoren's avatar Ronald Oussoren

Issue #14455: plistlib now supports binary plists and has an updated API.

This patch adds support for binary plists on OSX to plistlib (based
on a patch by 'dpounces').

The patch also cleans up the API for the plistlib module.
parent 243dfb44
...@@ -16,26 +16,21 @@ ...@@ -16,26 +16,21 @@
-------------- --------------
This module provides an interface for reading and writing the "property list" This module provides an interface for reading and writing the "property list"
XML files used mainly by Mac OS X. files used mainly by Mac OS X and supports both binary and XML plist files.
The property list (``.plist``) file format is a simple XML pickle supporting The property list (``.plist``) file format is a simple serialization supporting
basic object types, like dictionaries, lists, numbers and strings. Usually the basic object types, like dictionaries, lists, numbers and strings. Usually the
top level object is a dictionary. top level object is a dictionary.
To write out and to parse a plist file, use the :func:`writePlist` and To write out and to parse a plist file, use the :func:`dump` and
:func:`readPlist` functions. :func:`load` functions.
To work with plist data in bytes objects, use :func:`writePlistToBytes` To work with plist data in bytes objects, use :func:`dumps`
and :func:`readPlistFromBytes`. and :func:`loads`.
Values can be strings, integers, floats, booleans, tuples, lists, dictionaries Values can be strings, integers, floats, booleans, tuples, lists, dictionaries
(but only with string keys), :class:`Data` or :class:`datetime.datetime` (but only with string keys), :class:`Data`, :class:`bytes`, :class:`bytesarray`
objects. String values (including dictionary keys) have to be unicode strings -- or :class:`datetime.datetime` objects.
they will be written out as UTF-8.
The ``<data>`` plist type is supported through the :class:`Data` class. This is
a thin wrapper around a Python bytes object. Use :class:`Data` if your strings
contain control characters.
.. seealso:: .. seealso::
...@@ -45,37 +40,145 @@ contain control characters. ...@@ -45,37 +40,145 @@ contain control characters.
This module defines the following functions: This module defines the following functions:
.. function:: readPlist(pathOrFile) .. function:: load(fp, \*, fmt=None, use_builtin_types=True, dict_type=dict)
Read a plist file. *pathOrFile* may either be a file name or a (readable and Read a plist file. *fp* should be a readable and binary file object.
binary) file object. Return the unpacked root object (which usually is a Return the unpacked root object (which usually is a
dictionary). dictionary).
The XML data is parsed using the Expat parser from :mod:`xml.parsers.expat` The *fmt* is the format of the file and the following values are valid:
-- see its documentation for possible exceptions on ill-formed XML.
Unknown elements will simply be ignored by the plist parser. * :data:`None`: Autodetect the file format
* :data:`FMT_XML`: XML file format
* :data:`FMT_BINARY`: Binary plist format
If *use_builtin_types* is True (the default) binary data will be returned
as instances of :class:`bytes`, otherwise it is returned as instances of
:class:`Data`.
The *dict_type* is the type used for dictionaries that are read from the
plist file. The exact structure of the plist can be recovered by using
:class:`collections.OrderedDict` (although the order of keys shouldn't be
important in plist files).
XML data for the :data:`FMT_XML` format is parsed using the Expat parser
from :mod:`xml.parsers.expat` -- see its documentation for possible
exceptions on ill-formed XML. Unknown elements will simply be ignored
by the plist parser.
The parser for the binary format raises :exc:`InvalidFileException`
when the file cannot be parsed.
.. versionadded:: 3.4
.. function:: loads(data, \*, fmt=None, use_builtin_types=True, dict_type=dict)
Load a plist from a bytes object. See :func:`load` for an explanation of
the keyword arguments.
.. function:: dump(value, fp, \*, fmt=FMT_XML, sort_keys=True, skipkeys=False)
Write *value* to a plist file. *Fp* should be a writable, binary
file object.
The *fmt* argument specifies the format of the plist file and can be
one of the following values:
* :data:`FMT_XML`: XML formatted plist file
* :data:`FMT_BINARY`: Binary formatted plist file
When *sort_keys* is true (the default) the keys for dictionaries will be
written to the plist in sorted order, otherwise they will be written in
the iteration order of the dictionary.
When *skipkeys* is false (the default) the function raises :exc:`TypeError`
when a key of a dictionary is not a string, otherwise such keys are skipped.
A :exc:`TypeError` will be raised if the object is of an unsupported type or
a container that contains objects of unsupported types.
.. versionchanged:: 3.4
Added the *fmt*, *sort_keys* and *skipkeys* arguments.
.. function:: dumps(value, \*, fmt=FMT_XML, sort_keys=True, skipkeys=False)
Return *value* as a plist-formatted bytes object. See
the documentation for :func:`dump` for an explanation of the keyword
arguments of this function.
The following functions are deprecated:
.. function:: readPlist(pathOrFile)
Read a plist file. *pathOrFile* may be either a file name or a (readable
and binary) file object. Returns the unpacked root object (which usually
is a dictionary).
This function calls :func:`load` to do the actual work, the the documentation
of :func:`that function <load>` for an explanation of the keyword arguments.
.. note::
Dict values in the result have a ``__getattr__`` method that defers
to ``__getitem_``. This means that you can use attribute access to
access items of these dictionaries.
.. deprecated: 3.4 Use :func:`load` instead.
.. function:: writePlist(rootObject, pathOrFile) .. function:: writePlist(rootObject, pathOrFile)
Write *rootObject* to a plist file. *pathOrFile* may either be a file name Write *rootObject* to an XML plist file. *pathOrFile* may be either a file name
or a (writable and binary) file object. or a (writable and binary) file object
A :exc:`TypeError` will be raised if the object is of an unsupported type or .. deprecated: 3.4 Use :func:`dump` instead.
a container that contains objects of unsupported types.
.. function:: readPlistFromBytes(data) .. function:: readPlistFromBytes(data)
Read a plist data from a bytes object. Return the root object. Read a plist data from a bytes object. Return the root object.
See :func:`load` for a description of the keyword arguments.
.. note::
Dict values in the result have a ``__getattr__`` method that defers
to ``__getitem_``. This means that you can use attribute access to
access items of these dictionaries.
.. deprecated:: 3.4 Use :func:`loads` instead.
.. function:: writePlistToBytes(rootObject) .. function:: writePlistToBytes(rootObject)
Return *rootObject* as a plist-formatted bytes object. Return *rootObject* as an XML plist-formatted bytes object.
.. deprecated:: 3.4 Use :func:`dumps` instead.
.. versionchanged:: 3.4
Added the *fmt*, *sort_keys* and *skipkeys* arguments.
The following classes are available:
.. class:: Dict([dict]):
Return an extended mapping object with the same value as dictionary
*dict*.
This class is a subclass of :class:`dict` where attribute access can
be used to access items. That is, ``aDict.key`` is the same as
``aDict['key']`` for getting, setting and deleting items in the mapping.
.. deprecated:: 3.0
The following class is available:
.. class:: Data(data) .. class:: Data(data)
...@@ -86,6 +189,24 @@ The following class is available: ...@@ -86,6 +189,24 @@ The following class is available:
It has one attribute, :attr:`data`, that can be used to retrieve the Python It has one attribute, :attr:`data`, that can be used to retrieve the Python
bytes object stored in it. bytes object stored in it.
.. deprecated:: 3.4 Use a :class:`bytes` object instead
The following constants are avaiable:
.. data:: FMT_XML
The XML format for plist files.
.. versionadded:: 3.4
.. data:: FMT_BINARY
The binary format for plist files
.. versionadded:: 3.4
Examples Examples
-------- --------
...@@ -103,13 +224,15 @@ Generating a plist:: ...@@ -103,13 +224,15 @@ Generating a plist::
aTrueValue = True, aTrueValue = True,
aFalseValue = False, aFalseValue = False,
), ),
someData = Data(b"<binary gunk>"), someData = b"<binary gunk>",
someMoreData = Data(b"<lots of binary gunk>" * 10), someMoreData = b"<lots of binary gunk>" * 10,
aDate = datetime.datetime.fromtimestamp(time.mktime(time.gmtime())), aDate = datetime.datetime.fromtimestamp(time.mktime(time.gmtime())),
) )
writePlist(pl, fileName) with open(fileName, 'wb') as fp:
dump(pl, fp)
Parsing a plist:: Parsing a plist::
pl = readPlist(pathOrFile) with open(fileName, 'rb') as fp:
pl = load(fp)
print(pl["aKey"]) print(pl["aKey"])
This diff is collapsed.
This diff is collapsed.
#!/usr/bin/env python3
from Cocoa import NSMutableDictionary, NSMutableArray, NSString, NSDate
from Cocoa import NSPropertyListSerialization, NSPropertyListOpenStepFormat
from Cocoa import NSPropertyListXMLFormat_v1_0, NSPropertyListBinaryFormat_v1_0
from Cocoa import CFUUIDCreateFromString, NSNull, NSUUID, CFPropertyListCreateData
from Cocoa import NSURL
import datetime
from collections import OrderedDict
import binascii
FORMATS=[
# ('openstep', NSPropertyListOpenStepFormat),
('plistlib.FMT_XML', NSPropertyListXMLFormat_v1_0),
('plistlib.FMT_BINARY', NSPropertyListBinaryFormat_v1_0),
]
def nsstr(value):
return NSString.alloc().initWithString_(value)
def main():
pl = OrderedDict()
seconds = datetime.datetime(2004, 10, 26, 10, 33, 33, tzinfo=datetime.timezone(datetime.timedelta(0))).timestamp()
pl[nsstr('aDate')] = NSDate.dateWithTimeIntervalSince1970_(seconds)
pl[nsstr('aDict')] = d = OrderedDict()
d[nsstr('aFalseValue')] = False
d[nsstr('aTrueValue')] = True
d[nsstr('aUnicodeValue')] = "M\xe4ssig, Ma\xdf"
d[nsstr('anotherString')] = "<hello & 'hi' there!>"
d[nsstr('deeperDict')] = dd = OrderedDict()
dd[nsstr('a')] = 17
dd[nsstr('b')] = 32.5
dd[nsstr('c')] = a = NSMutableArray.alloc().init()
a.append(1)
a.append(2)
a.append(nsstr('text'))
pl[nsstr('aFloat')] = 0.5
pl[nsstr('aList')] = a = NSMutableArray.alloc().init()
a.append(nsstr('A'))
a.append(nsstr('B'))
a.append(12)
a.append(32.5)
aa = NSMutableArray.alloc().init()
a.append(aa)
aa.append(1)
aa.append(2)
aa.append(3)
pl[nsstr('aString')] = nsstr('Doodah')
pl[nsstr('anEmptyDict')] = NSMutableDictionary.alloc().init()
pl[nsstr('anEmptyList')] = NSMutableArray.alloc().init()
pl[nsstr('anInt')] = 728
pl[nsstr('nestedData')] = a = NSMutableArray.alloc().init()
a.append(b'''<lots of binary gunk>\x00\x01\x02\x03<lots of binary gunk>\x00\x01\x02\x03<lots of binary gunk>\x00\x01\x02\x03<lots of binary gunk>\x00\x01\x02\x03<lots of binary gunk>\x00\x01\x02\x03<lots of binary gunk>\x00\x01\x02\x03<lots of binary gunk>\x00\x01\x02\x03<lots of binary gunk>\x00\x01\x02\x03<lots of binary gunk>\x00\x01\x02\x03<lots of binary gunk>\x00\x01\x02\x03''')
pl[nsstr('someData')] = b'<binary gunk>'
pl[nsstr('someMoreData')] = b'''<lots of binary gunk>\x00\x01\x02\x03<lots of binary gunk>\x00\x01\x02\x03<lots of binary gunk>\x00\x01\x02\x03<lots of binary gunk>\x00\x01\x02\x03<lots of binary gunk>\x00\x01\x02\x03<lots of binary gunk>\x00\x01\x02\x03<lots of binary gunk>\x00\x01\x02\x03<lots of binary gunk>\x00\x01\x02\x03<lots of binary gunk>\x00\x01\x02\x03<lots of binary gunk>\x00\x01\x02\x03'''
pl[nsstr('\xc5benraa')] = nsstr("That was a unicode key.")
print("TESTDATA={")
for fmt_name, fmt_key in FORMATS:
data, error = NSPropertyListSerialization.dataWithPropertyList_format_options_error_(
pl, fmt_key, 0, None)
if data is None:
print("Cannot serialize", fmt_name, error)
else:
print(" %s: binascii.a2b_base64(b'''\n %s'''),"%(fmt_name, _encode_base64(bytes(data)).decode('ascii')[:-1]))
print("}")
print()
def _encode_base64(s, maxlinelength=60):
maxbinsize = (maxlinelength//4)*3
pieces = []
for i in range(0, len(s), maxbinsize):
chunk = s[i : i + maxbinsize]
pieces.append(binascii.b2a_base64(chunk))
return b' '.join(pieces)
main()
...@@ -59,6 +59,8 @@ Core and Builtins ...@@ -59,6 +59,8 @@ Core and Builtins
Library Library
------- -------
- Issue #14455: plistlib now supports binary plists and has an updated API.
- Issue #19633: Fixed writing not compressed 16- and 32-bit wave files on - Issue #19633: Fixed writing not compressed 16- and 32-bit wave files on
big-endian platforms. big-endian platforms.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment