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.
This commit is contained in:
parent
8455723cfb
commit
c5cf797342
|
@ -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.
|
||||||
|
|
||||||
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 class is available:
|
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
|
||||||
|
|
||||||
|
|
||||||
.. class:: Data(data)
|
.. class:: Data(data)
|
||||||
|
|
||||||
|
@ -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::
|
||||||
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"])
|
||||||
|
|
1098
Lib/plistlib.py
1098
Lib/plistlib.py
File diff suppressed because it is too large
Load Diff
|
@ -1,94 +1,87 @@
|
||||||
# Copyright (C) 2003 Python Software Foundation
|
# Copyright (C) 2003-2013 Python Software Foundation
|
||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
import plistlib
|
import plistlib
|
||||||
import os
|
import os
|
||||||
import datetime
|
import datetime
|
||||||
|
import codecs
|
||||||
|
import binascii
|
||||||
|
import collections
|
||||||
from test import support
|
from test import support
|
||||||
|
from io import BytesIO
|
||||||
|
|
||||||
|
ALL_FORMATS=(plistlib.FMT_XML, plistlib.FMT_BINARY)
|
||||||
|
|
||||||
# This test data was generated through Cocoa's NSDictionary class
|
# The testdata is generated using Mac/Tools/plistlib_generate_testdata.py
|
||||||
TESTDATA = b"""<?xml version="1.0" encoding="UTF-8"?>
|
# (which using PyObjC to control the Cocoa classes for generating plists)
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" \
|
TESTDATA={
|
||||||
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
plistlib.FMT_XML: binascii.a2b_base64(b'''
|
||||||
<plist version="1.0">
|
PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPCFET0NU
|
||||||
<dict>
|
WVBFIHBsaXN0IFBVQkxJQyAiLS8vQXBwbGUvL0RURCBQTElTVCAxLjAvL0VO
|
||||||
<key>aDate</key>
|
IiAiaHR0cDovL3d3dy5hcHBsZS5jb20vRFREcy9Qcm9wZXJ0eUxpc3QtMS4w
|
||||||
<date>2004-10-26T10:33:33Z</date>
|
LmR0ZCI+CjxwbGlzdCB2ZXJzaW9uPSIxLjAiPgo8ZGljdD4KCTxrZXk+YURh
|
||||||
<key>aDict</key>
|
dGU8L2tleT4KCTxkYXRlPjIwMDQtMTAtMjZUMTA6MzM6MzNaPC9kYXRlPgoJ
|
||||||
<dict>
|
PGtleT5hRGljdDwva2V5PgoJPGRpY3Q+CgkJPGtleT5hRmFsc2VWYWx1ZTwv
|
||||||
<key>aFalseValue</key>
|
a2V5PgoJCTxmYWxzZS8+CgkJPGtleT5hVHJ1ZVZhbHVlPC9rZXk+CgkJPHRy
|
||||||
<false/>
|
dWUvPgoJCTxrZXk+YVVuaWNvZGVWYWx1ZTwva2V5PgoJCTxzdHJpbmc+TcOk
|
||||||
<key>aTrueValue</key>
|
c3NpZywgTWHDnzwvc3RyaW5nPgoJCTxrZXk+YW5vdGhlclN0cmluZzwva2V5
|
||||||
<true/>
|
PgoJCTxzdHJpbmc+Jmx0O2hlbGxvICZhbXA7ICdoaScgdGhlcmUhJmd0Ozwv
|
||||||
<key>aUnicodeValue</key>
|
c3RyaW5nPgoJCTxrZXk+ZGVlcGVyRGljdDwva2V5PgoJCTxkaWN0PgoJCQk8
|
||||||
<string>M\xc3\xa4ssig, Ma\xc3\x9f</string>
|
a2V5PmE8L2tleT4KCQkJPGludGVnZXI+MTc8L2ludGVnZXI+CgkJCTxrZXk+
|
||||||
<key>anotherString</key>
|
Yjwva2V5PgoJCQk8cmVhbD4zMi41PC9yZWFsPgoJCQk8a2V5PmM8L2tleT4K
|
||||||
<string><hello & 'hi' there!></string>
|
CQkJPGFycmF5PgoJCQkJPGludGVnZXI+MTwvaW50ZWdlcj4KCQkJCTxpbnRl
|
||||||
<key>deeperDict</key>
|
Z2VyPjI8L2ludGVnZXI+CgkJCQk8c3RyaW5nPnRleHQ8L3N0cmluZz4KCQkJ
|
||||||
<dict>
|
PC9hcnJheT4KCQk8L2RpY3Q+Cgk8L2RpY3Q+Cgk8a2V5PmFGbG9hdDwva2V5
|
||||||
<key>a</key>
|
PgoJPHJlYWw+MC41PC9yZWFsPgoJPGtleT5hTGlzdDwva2V5PgoJPGFycmF5
|
||||||
<integer>17</integer>
|
PgoJCTxzdHJpbmc+QTwvc3RyaW5nPgoJCTxzdHJpbmc+Qjwvc3RyaW5nPgoJ
|
||||||
<key>b</key>
|
CTxpbnRlZ2VyPjEyPC9pbnRlZ2VyPgoJCTxyZWFsPjMyLjU8L3JlYWw+CgkJ
|
||||||
<real>32.5</real>
|
PGFycmF5PgoJCQk8aW50ZWdlcj4xPC9pbnRlZ2VyPgoJCQk8aW50ZWdlcj4y
|
||||||
<key>c</key>
|
PC9pbnRlZ2VyPgoJCQk8aW50ZWdlcj4zPC9pbnRlZ2VyPgoJCTwvYXJyYXk+
|
||||||
<array>
|
Cgk8L2FycmF5PgoJPGtleT5hU3RyaW5nPC9rZXk+Cgk8c3RyaW5nPkRvb2Rh
|
||||||
<integer>1</integer>
|
aDwvc3RyaW5nPgoJPGtleT5hbkVtcHR5RGljdDwva2V5PgoJPGRpY3QvPgoJ
|
||||||
<integer>2</integer>
|
PGtleT5hbkVtcHR5TGlzdDwva2V5PgoJPGFycmF5Lz4KCTxrZXk+YW5JbnQ8
|
||||||
<string>text</string>
|
L2tleT4KCTxpbnRlZ2VyPjcyODwvaW50ZWdlcj4KCTxrZXk+bmVzdGVkRGF0
|
||||||
</array>
|
YTwva2V5PgoJPGFycmF5PgoJCTxkYXRhPgoJCVBHeHZkSE1nYjJZZ1ltbHVZ
|
||||||
</dict>
|
WEo1SUdkMWJtcytBQUVDQXp4c2IzUnpJRzltSUdKcGJtRnllU0JuZFc1cgoJ
|
||||||
</dict>
|
CVBnQUJBZ004Ykc5MGN5QnZaaUJpYVc1aGNua2daM1Z1YXo0QUFRSURQR3h2
|
||||||
<key>aFloat</key>
|
ZEhNZ2IyWWdZbWx1WVhKNQoJCUlHZDFibXMrQUFFQ0F6eHNiM1J6SUc5bUlH
|
||||||
<real>0.5</real>
|
SnBibUZ5ZVNCbmRXNXJQZ0FCQWdNOGJHOTBjeUJ2WmlCaQoJCWFXNWhjbmtn
|
||||||
<key>aList</key>
|
WjNWdWF6NEFBUUlEUEd4dmRITWdiMllnWW1sdVlYSjVJR2QxYm1zK0FBRUNB
|
||||||
<array>
|
enhzYjNSegoJCUlHOW1JR0pwYm1GeWVTQm5kVzVyUGdBQkFnTThiRzkwY3lC
|
||||||
<string>A</string>
|
dlppQmlhVzVoY25rZ1ozVnVhejRBQVFJRAoJCVBHeHZkSE1nYjJZZ1ltbHVZ
|
||||||
<string>B</string>
|
WEo1SUdkMWJtcytBQUVDQXc9PQoJCTwvZGF0YT4KCTwvYXJyYXk+Cgk8a2V5
|
||||||
<integer>12</integer>
|
PnNvbWVEYXRhPC9rZXk+Cgk8ZGF0YT4KCVBHSnBibUZ5ZVNCbmRXNXJQZz09
|
||||||
<real>32.5</real>
|
Cgk8L2RhdGE+Cgk8a2V5PnNvbWVNb3JlRGF0YTwva2V5PgoJPGRhdGE+CglQ
|
||||||
<array>
|
R3h2ZEhNZ2IyWWdZbWx1WVhKNUlHZDFibXMrQUFFQ0F6eHNiM1J6SUc5bUlH
|
||||||
<integer>1</integer>
|
SnBibUZ5ZVNCbmRXNXJQZ0FCQWdNOAoJYkc5MGN5QnZaaUJpYVc1aGNua2da
|
||||||
<integer>2</integer>
|
M1Z1YXo0QUFRSURQR3h2ZEhNZ2IyWWdZbWx1WVhKNUlHZDFibXMrQUFFQ0F6
|
||||||
<integer>3</integer>
|
eHMKCWIzUnpJRzltSUdKcGJtRnllU0JuZFc1clBnQUJBZ004Ykc5MGN5QnZa
|
||||||
</array>
|
aUJpYVc1aGNua2daM1Z1YXo0QUFRSURQR3h2CglkSE1nYjJZZ1ltbHVZWEo1
|
||||||
</array>
|
SUdkMWJtcytBQUVDQXp4c2IzUnpJRzltSUdKcGJtRnllU0JuZFc1clBnQUJB
|
||||||
<key>aString</key>
|
Z004Ykc5MAoJY3lCdlppQmlhVzVoY25rZ1ozVnVhejRBQVFJRFBHeHZkSE1n
|
||||||
<string>Doodah</string>
|
YjJZZ1ltbHVZWEo1SUdkMWJtcytBQUVDQXc9PQoJPC9kYXRhPgoJPGtleT7D
|
||||||
<key>anEmptyDict</key>
|
hWJlbnJhYTwva2V5PgoJPHN0cmluZz5UaGF0IHdhcyBhIHVuaWNvZGUga2V5
|
||||||
<dict/>
|
Ljwvc3RyaW5nPgo8L2RpY3Q+CjwvcGxpc3Q+Cg=='''),
|
||||||
<key>anEmptyList</key>
|
plistlib.FMT_BINARY: binascii.a2b_base64(b'''
|
||||||
<array/>
|
YnBsaXN0MDDcAQIDBAUGBwgJCgsMDQ4iIykqKywtLy4wVWFEYXRlVWFEaWN0
|
||||||
<key>anInt</key>
|
VmFGbG9hdFVhTGlzdFdhU3RyaW5nW2FuRW1wdHlEaWN0W2FuRW1wdHlMaXN0
|
||||||
<integer>728</integer>
|
VWFuSW50Wm5lc3RlZERhdGFYc29tZURhdGFcc29tZU1vcmVEYXRhZwDFAGIA
|
||||||
<key>nestedData</key>
|
ZQBuAHIAYQBhM0GcuX30AAAA1Q8QERITFBUWFxhbYUZhbHNlVmFsdWVaYVRy
|
||||||
<array>
|
dWVWYWx1ZV1hVW5pY29kZVZhbHVlXWFub3RoZXJTdHJpbmdaZGVlcGVyRGlj
|
||||||
<data>
|
dAgJawBNAOQAcwBzAGkAZwAsACAATQBhAN9fEBU8aGVsbG8gJiAnaGknIHRo
|
||||||
PGxvdHMgb2YgYmluYXJ5IGd1bms+AAECAzxsb3RzIG9mIGJpbmFyeSBndW5r
|
ZXJlIT7TGRobHB0eUWFRYlFjEBEjQEBAAAAAAACjHyAhEAEQAlR0ZXh0Iz/g
|
||||||
PgABAgM8bG90cyBvZiBiaW5hcnkgZ3Vuaz4AAQIDPGxvdHMgb2YgYmluYXJ5
|
AAAAAAAApSQlJh0nUUFRQhAMox8gKBADVkRvb2RhaNCgEQLYoS5PEPo8bG90
|
||||||
IGd1bms+AAECAzxsb3RzIG9mIGJpbmFyeSBndW5rPgABAgM8bG90cyBvZiBi
|
cyBvZiBiaW5hcnkgZ3Vuaz4AAQIDPGxvdHMgb2YgYmluYXJ5IGd1bms+AAEC
|
||||||
aW5hcnkgZ3Vuaz4AAQIDPGxvdHMgb2YgYmluYXJ5IGd1bms+AAECAzxsb3Rz
|
Azxsb3RzIG9mIGJpbmFyeSBndW5rPgABAgM8bG90cyBvZiBiaW5hcnkgZ3Vu
|
||||||
IG9mIGJpbmFyeSBndW5rPgABAgM8bG90cyBvZiBiaW5hcnkgZ3Vuaz4AAQID
|
az4AAQIDPGxvdHMgb2YgYmluYXJ5IGd1bms+AAECAzxsb3RzIG9mIGJpbmFy
|
||||||
PGxvdHMgb2YgYmluYXJ5IGd1bms+AAECAw==
|
eSBndW5rPgABAgM8bG90cyBvZiBiaW5hcnkgZ3Vuaz4AAQIDPGxvdHMgb2Yg
|
||||||
</data>
|
YmluYXJ5IGd1bms+AAECAzxsb3RzIG9mIGJpbmFyeSBndW5rPgABAgM8bG90
|
||||||
</array>
|
cyBvZiBiaW5hcnkgZ3Vuaz4AAQIDTTxiaW5hcnkgZ3Vuaz5fEBdUaGF0IHdh
|
||||||
<key>someData</key>
|
cyBhIHVuaWNvZGUga2V5LgAIACEAJwAtADQAOgBCAE4AWgBgAGsAdACBAJAA
|
||||||
<data>
|
mQCkALAAuwDJANcA4gDjAOQA+wETARoBHAEeASABIgErAS8BMQEzATgBQQFH
|
||||||
PGJpbmFyeSBndW5rPg==
|
AUkBSwFNAVEBUwFaAVsBXAFfAWECXgJsAAAAAAAAAgEAAAAAAAAAMQAAAAAA
|
||||||
</data>
|
AAAAAAAAAAAAAoY='''),
|
||||||
<key>someMoreData</key>
|
}
|
||||||
<data>
|
|
||||||
PGxvdHMgb2YgYmluYXJ5IGd1bms+AAECAzxsb3RzIG9mIGJpbmFyeSBndW5rPgABAgM8
|
|
||||||
bG90cyBvZiBiaW5hcnkgZ3Vuaz4AAQIDPGxvdHMgb2YgYmluYXJ5IGd1bms+AAECAzxs
|
|
||||||
b3RzIG9mIGJpbmFyeSBndW5rPgABAgM8bG90cyBvZiBiaW5hcnkgZ3Vuaz4AAQIDPGxv
|
|
||||||
dHMgb2YgYmluYXJ5IGd1bms+AAECAzxsb3RzIG9mIGJpbmFyeSBndW5rPgABAgM8bG90
|
|
||||||
cyBvZiBiaW5hcnkgZ3Vuaz4AAQIDPGxvdHMgb2YgYmluYXJ5IGd1bms+AAECAw==
|
|
||||||
</data>
|
|
||||||
<key>\xc3\x85benraa</key>
|
|
||||||
<string>That was a unicode key.</string>
|
|
||||||
</dict>
|
|
||||||
</plist>
|
|
||||||
""".replace(b" " * 8, b"\t") # Apple as well as plistlib.py output hard tabs
|
|
||||||
|
|
||||||
|
|
||||||
class TestPlistlib(unittest.TestCase):
|
class TestPlistlib(unittest.TestCase):
|
||||||
|
@ -99,7 +92,7 @@ class TestPlistlib(unittest.TestCase):
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def _create(self):
|
def _create(self, fmt=None):
|
||||||
pl = dict(
|
pl = dict(
|
||||||
aString="Doodah",
|
aString="Doodah",
|
||||||
aList=["A", "B", 12, 32.5, [1, 2, 3]],
|
aList=["A", "B", 12, 32.5, [1, 2, 3]],
|
||||||
|
@ -112,9 +105,9 @@ class TestPlistlib(unittest.TestCase):
|
||||||
aFalseValue=False,
|
aFalseValue=False,
|
||||||
deeperDict=dict(a=17, b=32.5, c=[1, 2, "text"]),
|
deeperDict=dict(a=17, b=32.5, c=[1, 2, "text"]),
|
||||||
),
|
),
|
||||||
someData = plistlib.Data(b"<binary gunk>"),
|
someData = b"<binary gunk>",
|
||||||
someMoreData = plistlib.Data(b"<lots of binary gunk>\0\1\2\3" * 10),
|
someMoreData = b"<lots of binary gunk>\0\1\2\3" * 10,
|
||||||
nestedData = [plistlib.Data(b"<lots of binary gunk>\0\1\2\3" * 10)],
|
nestedData = [b"<lots of binary gunk>\0\1\2\3" * 10],
|
||||||
aDate = datetime.datetime(2004, 10, 26, 10, 33, 33),
|
aDate = datetime.datetime(2004, 10, 26, 10, 33, 33),
|
||||||
anEmptyDict = dict(),
|
anEmptyDict = dict(),
|
||||||
anEmptyList = list()
|
anEmptyList = list()
|
||||||
|
@ -129,67 +122,211 @@ class TestPlistlib(unittest.TestCase):
|
||||||
|
|
||||||
def test_io(self):
|
def test_io(self):
|
||||||
pl = self._create()
|
pl = self._create()
|
||||||
plistlib.writePlist(pl, support.TESTFN)
|
with open(support.TESTFN, 'wb') as fp:
|
||||||
pl2 = plistlib.readPlist(support.TESTFN)
|
plistlib.dump(pl, fp)
|
||||||
|
|
||||||
|
with open(support.TESTFN, 'rb') as fp:
|
||||||
|
pl2 = plistlib.load(fp)
|
||||||
|
|
||||||
self.assertEqual(dict(pl), dict(pl2))
|
self.assertEqual(dict(pl), dict(pl2))
|
||||||
|
|
||||||
|
self.assertRaises(AttributeError, plistlib.dump, pl, 'filename')
|
||||||
|
self.assertRaises(AttributeError, plistlib.load, 'filename')
|
||||||
|
|
||||||
|
|
||||||
def test_bytes(self):
|
def test_bytes(self):
|
||||||
pl = self._create()
|
pl = self._create()
|
||||||
data = plistlib.writePlistToBytes(pl)
|
data = plistlib.dumps(pl)
|
||||||
pl2 = plistlib.readPlistFromBytes(data)
|
pl2 = plistlib.loads(data)
|
||||||
|
self.assertNotIsInstance(pl, plistlib._InternalDict)
|
||||||
self.assertEqual(dict(pl), dict(pl2))
|
self.assertEqual(dict(pl), dict(pl2))
|
||||||
data2 = plistlib.writePlistToBytes(pl2)
|
data2 = plistlib.dumps(pl2)
|
||||||
self.assertEqual(data, data2)
|
self.assertEqual(data, data2)
|
||||||
|
|
||||||
def test_indentation_array(self):
|
def test_indentation_array(self):
|
||||||
data = [[[[[[[[{'test': plistlib.Data(b'aaaaaa')}]]]]]]]]
|
data = [[[[[[[[{'test': b'aaaaaa'}]]]]]]]]
|
||||||
self.assertEqual(plistlib.readPlistFromBytes(plistlib.writePlistToBytes(data)), data)
|
self.assertEqual(plistlib.loads(plistlib.dumps(data)), data)
|
||||||
|
|
||||||
def test_indentation_dict(self):
|
def test_indentation_dict(self):
|
||||||
data = {'1': {'2': {'3': {'4': {'5': {'6': {'7': {'8': {'9': plistlib.Data(b'aaaaaa')}}}}}}}}}
|
data = {'1': {'2': {'3': {'4': {'5': {'6': {'7': {'8': {'9': b'aaaaaa'}}}}}}}}}
|
||||||
self.assertEqual(plistlib.readPlistFromBytes(plistlib.writePlistToBytes(data)), data)
|
self.assertEqual(plistlib.loads(plistlib.dumps(data)), data)
|
||||||
|
|
||||||
def test_indentation_dict_mix(self):
|
def test_indentation_dict_mix(self):
|
||||||
data = {'1': {'2': [{'3': [[[[[{'test': plistlib.Data(b'aaaaaa')}]]]]]}]}}
|
data = {'1': {'2': [{'3': [[[[[{'test': b'aaaaaa'}]]]]]}]}}
|
||||||
self.assertEqual(plistlib.readPlistFromBytes(plistlib.writePlistToBytes(data)), data)
|
self.assertEqual(plistlib.loads(plistlib.dumps(data)), data)
|
||||||
|
|
||||||
def test_appleformatting(self):
|
def test_appleformatting(self):
|
||||||
pl = plistlib.readPlistFromBytes(TESTDATA)
|
for use_builtin_types in (True, False):
|
||||||
data = plistlib.writePlistToBytes(pl)
|
for fmt in ALL_FORMATS:
|
||||||
self.assertEqual(data, TESTDATA,
|
with self.subTest(fmt=fmt, use_builtin_types=use_builtin_types):
|
||||||
|
pl = plistlib.loads(TESTDATA[fmt],
|
||||||
|
use_builtin_types=use_builtin_types)
|
||||||
|
data = plistlib.dumps(pl, fmt=fmt)
|
||||||
|
self.assertEqual(data, TESTDATA[fmt],
|
||||||
"generated data was not identical to Apple's output")
|
"generated data was not identical to Apple's output")
|
||||||
|
|
||||||
|
|
||||||
def test_appleformattingfromliteral(self):
|
def test_appleformattingfromliteral(self):
|
||||||
pl = self._create()
|
self.maxDiff = None
|
||||||
pl2 = plistlib.readPlistFromBytes(TESTDATA)
|
for fmt in ALL_FORMATS:
|
||||||
|
with self.subTest(fmt=fmt):
|
||||||
|
pl = self._create(fmt=fmt)
|
||||||
|
pl2 = plistlib.loads(TESTDATA[fmt])
|
||||||
self.assertEqual(dict(pl), dict(pl2),
|
self.assertEqual(dict(pl), dict(pl2),
|
||||||
"generated data was not identical to Apple's output")
|
"generated data was not identical to Apple's output")
|
||||||
|
|
||||||
def test_bytesio(self):
|
def test_bytesio(self):
|
||||||
from io import BytesIO
|
for fmt in ALL_FORMATS:
|
||||||
|
with self.subTest(fmt=fmt):
|
||||||
b = BytesIO()
|
b = BytesIO()
|
||||||
pl = self._create()
|
pl = self._create(fmt=fmt)
|
||||||
plistlib.writePlist(pl, b)
|
plistlib.dump(pl, b, fmt=fmt)
|
||||||
pl2 = plistlib.readPlist(BytesIO(b.getvalue()))
|
pl2 = plistlib.load(BytesIO(b.getvalue()))
|
||||||
self.assertEqual(dict(pl), dict(pl2))
|
self.assertEqual(dict(pl), dict(pl2))
|
||||||
|
|
||||||
|
def test_keysort_bytesio(self):
|
||||||
|
pl = collections.OrderedDict()
|
||||||
|
pl['b'] = 1
|
||||||
|
pl['a'] = 2
|
||||||
|
pl['c'] = 3
|
||||||
|
|
||||||
|
for fmt in ALL_FORMATS:
|
||||||
|
for sort_keys in (False, True):
|
||||||
|
with self.subTest(fmt=fmt, sort_keys=sort_keys):
|
||||||
|
b = BytesIO()
|
||||||
|
|
||||||
|
plistlib.dump(pl, b, fmt=fmt, sort_keys=sort_keys)
|
||||||
|
pl2 = plistlib.load(BytesIO(b.getvalue()),
|
||||||
|
dict_type=collections.OrderedDict)
|
||||||
|
|
||||||
|
self.assertEqual(dict(pl), dict(pl2))
|
||||||
|
if sort_keys:
|
||||||
|
self.assertEqual(list(pl2.keys()), ['a', 'b', 'c'])
|
||||||
|
else:
|
||||||
|
self.assertEqual(list(pl2.keys()), ['b', 'a', 'c'])
|
||||||
|
|
||||||
|
def test_keysort(self):
|
||||||
|
pl = collections.OrderedDict()
|
||||||
|
pl['b'] = 1
|
||||||
|
pl['a'] = 2
|
||||||
|
pl['c'] = 3
|
||||||
|
|
||||||
|
for fmt in ALL_FORMATS:
|
||||||
|
for sort_keys in (False, True):
|
||||||
|
with self.subTest(fmt=fmt, sort_keys=sort_keys):
|
||||||
|
data = plistlib.dumps(pl, fmt=fmt, sort_keys=sort_keys)
|
||||||
|
pl2 = plistlib.loads(data, dict_type=collections.OrderedDict)
|
||||||
|
|
||||||
|
self.assertEqual(dict(pl), dict(pl2))
|
||||||
|
if sort_keys:
|
||||||
|
self.assertEqual(list(pl2.keys()), ['a', 'b', 'c'])
|
||||||
|
else:
|
||||||
|
self.assertEqual(list(pl2.keys()), ['b', 'a', 'c'])
|
||||||
|
|
||||||
|
def test_keys_no_string(self):
|
||||||
|
pl = { 42: 'aNumber' }
|
||||||
|
|
||||||
|
for fmt in ALL_FORMATS:
|
||||||
|
with self.subTest(fmt=fmt):
|
||||||
|
self.assertRaises(TypeError, plistlib.dumps, pl, fmt=fmt)
|
||||||
|
|
||||||
|
b = BytesIO()
|
||||||
|
self.assertRaises(TypeError, plistlib.dump, pl, b, fmt=fmt)
|
||||||
|
|
||||||
|
def test_skipkeys(self):
|
||||||
|
pl = {
|
||||||
|
42: 'aNumber',
|
||||||
|
'snake': 'aWord',
|
||||||
|
}
|
||||||
|
|
||||||
|
for fmt in ALL_FORMATS:
|
||||||
|
with self.subTest(fmt=fmt):
|
||||||
|
data = plistlib.dumps(
|
||||||
|
pl, fmt=fmt, skipkeys=True, sort_keys=False)
|
||||||
|
|
||||||
|
pl2 = plistlib.loads(data)
|
||||||
|
self.assertEqual(pl2, {'snake': 'aWord'})
|
||||||
|
|
||||||
|
fp = BytesIO()
|
||||||
|
plistlib.dump(
|
||||||
|
pl, fp, fmt=fmt, skipkeys=True, sort_keys=False)
|
||||||
|
data = fp.getvalue()
|
||||||
|
pl2 = plistlib.loads(fp.getvalue())
|
||||||
|
self.assertEqual(pl2, {'snake': 'aWord'})
|
||||||
|
|
||||||
|
def test_tuple_members(self):
|
||||||
|
pl = {
|
||||||
|
'first': (1, 2),
|
||||||
|
'second': (1, 2),
|
||||||
|
'third': (3, 4),
|
||||||
|
}
|
||||||
|
|
||||||
|
for fmt in ALL_FORMATS:
|
||||||
|
with self.subTest(fmt=fmt):
|
||||||
|
data = plistlib.dumps(pl, fmt=fmt)
|
||||||
|
pl2 = plistlib.loads(data)
|
||||||
|
self.assertEqual(pl2, {
|
||||||
|
'first': [1, 2],
|
||||||
|
'second': [1, 2],
|
||||||
|
'third': [3, 4],
|
||||||
|
})
|
||||||
|
self.assertIsNot(pl2['first'], pl2['second'])
|
||||||
|
|
||||||
|
def test_list_members(self):
|
||||||
|
pl = {
|
||||||
|
'first': [1, 2],
|
||||||
|
'second': [1, 2],
|
||||||
|
'third': [3, 4],
|
||||||
|
}
|
||||||
|
|
||||||
|
for fmt in ALL_FORMATS:
|
||||||
|
with self.subTest(fmt=fmt):
|
||||||
|
data = plistlib.dumps(pl, fmt=fmt)
|
||||||
|
pl2 = plistlib.loads(data)
|
||||||
|
self.assertEqual(pl2, {
|
||||||
|
'first': [1, 2],
|
||||||
|
'second': [1, 2],
|
||||||
|
'third': [3, 4],
|
||||||
|
})
|
||||||
|
self.assertIsNot(pl2['first'], pl2['second'])
|
||||||
|
|
||||||
|
def test_dict_members(self):
|
||||||
|
pl = {
|
||||||
|
'first': {'a': 1},
|
||||||
|
'second': {'a': 1},
|
||||||
|
'third': {'b': 2 },
|
||||||
|
}
|
||||||
|
|
||||||
|
for fmt in ALL_FORMATS:
|
||||||
|
with self.subTest(fmt=fmt):
|
||||||
|
data = plistlib.dumps(pl, fmt=fmt)
|
||||||
|
pl2 = plistlib.loads(data)
|
||||||
|
self.assertEqual(pl2, {
|
||||||
|
'first': {'a': 1},
|
||||||
|
'second': {'a': 1},
|
||||||
|
'third': {'b': 2 },
|
||||||
|
})
|
||||||
|
self.assertIsNot(pl2['first'], pl2['second'])
|
||||||
|
|
||||||
def test_controlcharacters(self):
|
def test_controlcharacters(self):
|
||||||
for i in range(128):
|
for i in range(128):
|
||||||
c = chr(i)
|
c = chr(i)
|
||||||
testString = "string containing %s" % c
|
testString = "string containing %s" % c
|
||||||
if i >= 32 or c in "\r\n\t":
|
if i >= 32 or c in "\r\n\t":
|
||||||
# \r, \n and \t are the only legal control chars in XML
|
# \r, \n and \t are the only legal control chars in XML
|
||||||
plistlib.writePlistToBytes(testString)
|
plistlib.dumps(testString, fmt=plistlib.FMT_XML)
|
||||||
else:
|
else:
|
||||||
self.assertRaises(ValueError,
|
self.assertRaises(ValueError,
|
||||||
plistlib.writePlistToBytes,
|
plistlib.dumps,
|
||||||
testString)
|
testString)
|
||||||
|
|
||||||
def test_nondictroot(self):
|
def test_nondictroot(self):
|
||||||
|
for fmt in ALL_FORMATS:
|
||||||
|
with self.subTest(fmt=fmt):
|
||||||
test1 = "abc"
|
test1 = "abc"
|
||||||
test2 = [1, 2, 3, "abc"]
|
test2 = [1, 2, 3, "abc"]
|
||||||
result1 = plistlib.readPlistFromBytes(plistlib.writePlistToBytes(test1))
|
result1 = plistlib.loads(plistlib.dumps(test1, fmt=fmt))
|
||||||
result2 = plistlib.readPlistFromBytes(plistlib.writePlistToBytes(test2))
|
result2 = plistlib.loads(plistlib.dumps(test2, fmt=fmt))
|
||||||
self.assertEqual(test1, result1)
|
self.assertEqual(test1, result1)
|
||||||
self.assertEqual(test2, result2)
|
self.assertEqual(test2, result2)
|
||||||
|
|
||||||
|
@ -197,7 +334,7 @@ class TestPlistlib(unittest.TestCase):
|
||||||
for i in ["<key>key inside an array</key>",
|
for i in ["<key>key inside an array</key>",
|
||||||
"<key>key inside an array2</key><real>3</real>",
|
"<key>key inside an array2</key><real>3</real>",
|
||||||
"<true/><key>key inside an array3</key>"]:
|
"<true/><key>key inside an array3</key>"]:
|
||||||
self.assertRaises(ValueError, plistlib.readPlistFromBytes,
|
self.assertRaises(ValueError, plistlib.loads,
|
||||||
("<plist><array>%s</array></plist>"%i).encode())
|
("<plist><array>%s</array></plist>"%i).encode())
|
||||||
|
|
||||||
def test_invaliddict(self):
|
def test_invaliddict(self):
|
||||||
|
@ -206,22 +343,130 @@ class TestPlistlib(unittest.TestCase):
|
||||||
"<string>missing key</string>",
|
"<string>missing key</string>",
|
||||||
"<key>k1</key><string>v1</string><real>5.3</real>"
|
"<key>k1</key><string>v1</string><real>5.3</real>"
|
||||||
"<key>k1</key><key>k2</key><string>double key</string>"]:
|
"<key>k1</key><key>k2</key><string>double key</string>"]:
|
||||||
self.assertRaises(ValueError, plistlib.readPlistFromBytes,
|
self.assertRaises(ValueError, plistlib.loads,
|
||||||
("<plist><dict>%s</dict></plist>"%i).encode())
|
("<plist><dict>%s</dict></plist>"%i).encode())
|
||||||
self.assertRaises(ValueError, plistlib.readPlistFromBytes,
|
self.assertRaises(ValueError, plistlib.loads,
|
||||||
("<plist><array><dict>%s</dict></array></plist>"%i).encode())
|
("<plist><array><dict>%s</dict></array></plist>"%i).encode())
|
||||||
|
|
||||||
def test_invalidinteger(self):
|
def test_invalidinteger(self):
|
||||||
self.assertRaises(ValueError, plistlib.readPlistFromBytes,
|
self.assertRaises(ValueError, plistlib.loads,
|
||||||
b"<plist><integer>not integer</integer></plist>")
|
b"<plist><integer>not integer</integer></plist>")
|
||||||
|
|
||||||
def test_invalidreal(self):
|
def test_invalidreal(self):
|
||||||
self.assertRaises(ValueError, plistlib.readPlistFromBytes,
|
self.assertRaises(ValueError, plistlib.loads,
|
||||||
b"<plist><integer>not real</integer></plist>")
|
b"<plist><integer>not real</integer></plist>")
|
||||||
|
|
||||||
|
def test_xml_encodings(self):
|
||||||
|
base = TESTDATA[plistlib.FMT_XML]
|
||||||
|
|
||||||
|
for xml_encoding, encoding, bom in [
|
||||||
|
(b'utf-8', 'utf-8', codecs.BOM_UTF8),
|
||||||
|
(b'utf-16', 'utf-16-le', codecs.BOM_UTF16_LE),
|
||||||
|
(b'utf-16', 'utf-16-be', codecs.BOM_UTF16_BE),
|
||||||
|
# Expat does not support UTF-32
|
||||||
|
#(b'utf-32', 'utf-32-le', codecs.BOM_UTF32_LE),
|
||||||
|
#(b'utf-32', 'utf-32-be', codecs.BOM_UTF32_BE),
|
||||||
|
]:
|
||||||
|
|
||||||
|
pl = self._create(fmt=plistlib.FMT_XML)
|
||||||
|
with self.subTest(encoding=encoding):
|
||||||
|
data = base.replace(b'UTF-8', xml_encoding)
|
||||||
|
data = bom + data.decode('utf-8').encode(encoding)
|
||||||
|
pl2 = plistlib.loads(data)
|
||||||
|
self.assertEqual(dict(pl), dict(pl2))
|
||||||
|
|
||||||
|
|
||||||
|
class TestPlistlibDeprecated(unittest.TestCase):
|
||||||
|
def test_io_deprecated(self):
|
||||||
|
pl_in = {
|
||||||
|
'key': 42,
|
||||||
|
'sub': {
|
||||||
|
'key': 9,
|
||||||
|
'alt': 'value',
|
||||||
|
'data': b'buffer',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pl_out = plistlib._InternalDict({
|
||||||
|
'key': 42,
|
||||||
|
'sub': plistlib._InternalDict({
|
||||||
|
'key': 9,
|
||||||
|
'alt': 'value',
|
||||||
|
'data': plistlib.Data(b'buffer'),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
self.addCleanup(support.unlink, support.TESTFN)
|
||||||
|
with self.assertWarns(DeprecationWarning):
|
||||||
|
plistlib.writePlist(pl_in, support.TESTFN)
|
||||||
|
|
||||||
|
with self.assertWarns(DeprecationWarning):
|
||||||
|
pl2 = plistlib.readPlist(support.TESTFN)
|
||||||
|
|
||||||
|
self.assertEqual(pl_out, pl2)
|
||||||
|
|
||||||
|
os.unlink(support.TESTFN)
|
||||||
|
|
||||||
|
with open(support.TESTFN, 'wb') as fp:
|
||||||
|
with self.assertWarns(DeprecationWarning):
|
||||||
|
plistlib.writePlist(pl_in, fp)
|
||||||
|
|
||||||
|
with open(support.TESTFN, 'rb') as fp:
|
||||||
|
with self.assertWarns(DeprecationWarning):
|
||||||
|
pl2 = plistlib.readPlist(fp)
|
||||||
|
|
||||||
|
self.assertEqual(pl_out, pl2)
|
||||||
|
|
||||||
|
def test_bytes_deprecated(self):
|
||||||
|
pl = {
|
||||||
|
'key': 42,
|
||||||
|
'sub': {
|
||||||
|
'key': 9,
|
||||||
|
'alt': 'value',
|
||||||
|
'data': b'buffer',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
with self.assertWarns(DeprecationWarning):
|
||||||
|
data = plistlib.writePlistToBytes(pl)
|
||||||
|
|
||||||
|
with self.assertWarns(DeprecationWarning):
|
||||||
|
pl2 = plistlib.readPlistFromBytes(data)
|
||||||
|
|
||||||
|
self.assertIsInstance(pl2, plistlib._InternalDict)
|
||||||
|
self.assertEqual(pl2, plistlib._InternalDict(
|
||||||
|
key=42,
|
||||||
|
sub=plistlib._InternalDict(
|
||||||
|
key=9,
|
||||||
|
alt='value',
|
||||||
|
data=plistlib.Data(b'buffer'),
|
||||||
|
)
|
||||||
|
))
|
||||||
|
|
||||||
|
with self.assertWarns(DeprecationWarning):
|
||||||
|
data2 = plistlib.writePlistToBytes(pl2)
|
||||||
|
self.assertEqual(data, data2)
|
||||||
|
|
||||||
|
def test_dataobject_deprecated(self):
|
||||||
|
in_data = { 'key': plistlib.Data(b'hello') }
|
||||||
|
out_data = { 'key': b'hello' }
|
||||||
|
|
||||||
|
buf = plistlib.dumps(in_data)
|
||||||
|
|
||||||
|
cur = plistlib.loads(buf)
|
||||||
|
self.assertEqual(cur, out_data)
|
||||||
|
self.assertNotEqual(cur, in_data)
|
||||||
|
|
||||||
|
cur = plistlib.loads(buf, use_builtin_types=False)
|
||||||
|
self.assertNotEqual(cur, out_data)
|
||||||
|
self.assertEqual(cur, in_data)
|
||||||
|
|
||||||
|
with self.assertWarns(DeprecationWarning):
|
||||||
|
cur = plistlib.readPlistFromBytes(buf)
|
||||||
|
self.assertNotEqual(cur, out_data)
|
||||||
|
self.assertEqual(cur, in_data)
|
||||||
|
|
||||||
|
|
||||||
def test_main():
|
def test_main():
|
||||||
support.run_unittest(TestPlistlib)
|
support.run_unittest(TestPlistlib, TestPlistlibDeprecated)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|
|
@ -0,0 +1,94 @@
|
||||||
|
#!/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
|
||||||
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.
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue