__all__ = [
"readPlist", "writePlist", "readPlistFromBytes", "writePlistToBytes",
"Data", "InvalidFileException", "FMT_XML", "FMT_BINARY",
- "load", "dump", "loads", "dumps"
+ "load", "dump", "loads", "dumps", "UID"
]
import binascii
#
+class UID:
+ def __init__(self, data):
+ if not isinstance(data, int):
+ raise TypeError("data must be an int")
+ if data >= 1 << 64:
+ raise ValueError("UIDs cannot be >= 2**64")
+ if data < 0:
+ raise ValueError("UIDs must be positive")
+ self.data = data
+
+ def __index__(self):
+ return self.data
+
+ def __repr__(self):
+ return "%s(%s)" % (self.__class__.__name__, repr(self.data))
+
+ def __reduce__(self):
+ return self.__class__, (self.data,)
+
+ def __eq__(self, other):
+ if not isinstance(other, UID):
+ return NotImplemented
+ return self.data == other.data
+
+ def __hash__(self):
+ return hash(self.data)
+
+
#
# XML support
#
s = self._get_size(tokenL)
result = self._fp.read(s * 2).decode('utf-16be')
- # tokenH == 0x80 is documented as 'UID' and appears to be used for
- # keyed-archiving, not in plists.
+ elif tokenH == 0x80: # UID
+ # used by Key-Archiver plist files
+ result = UID(int.from_bytes(self._fp.read(1 + tokenL), 'big'))
elif tokenH == 0xA0: # array
s = self._get_size(tokenL)
self._fp.write(t)
+ elif isinstance(value, UID):
+ if value.data < 0:
+ raise ValueError("UIDs must be positive")
+ elif value.data < 1 << 8:
+ self._fp.write(struct.pack('>BB', 0x80, value))
+ elif value.data < 1 << 16:
+ self._fp.write(struct.pack('>BH', 0x81, value))
+ elif value.data < 1 << 32:
+ self._fp.write(struct.pack('>BL', 0x83, value))
+ elif value.data < 1 << 64:
+ self._fp.write(struct.pack('>BQ', 0x87, value))
+ else:
+ raise OverflowError(value)
+
elif isinstance(value, (list, tuple)):
refs = [self._getrefnum(o) for o in value]
s = len(refs)
# Copyright (C) 2003-2013 Python Software Foundation
-
+import copy
+import operator
+import pickle
import unittest
import plistlib
import os
from test import support
from io import BytesIO
+from plistlib import UID
+
ALL_FORMATS=(plistlib.FMT_XML, plistlib.FMT_BINARY)
# The testdata is generated using Mac/Tools/plistlib_generate_testdata.py
ZwB0AHwAiACUAJoApQCuALsAygDTAOQA7QD4AQQBDwEdASsBNgE3ATgBTwFn
AW4BcAFyAXQBdgF/AYMBhQGHAYwBlQGbAZ0BnwGhAaUBpwGwAbkBwAHBAcIB
xQHHAsQC0gAAAAAAAAIBAAAAAAAAADkAAAAAAAAAAAAAAAAAAALs'''),
+ 'KEYED_ARCHIVE': binascii.a2b_base64(b'''
+ YnBsaXN0MDDUAQIDBAUGHB1YJHZlcnNpb25YJG9iamVjdHNZJGFyY2hpdmVy
+ VCR0b3ASAAGGoKMHCA9VJG51bGzTCQoLDA0OVnB5dHlwZVYkY2xhc3NZTlMu
+ c3RyaW5nEAGAAl8QE0tleUFyY2hpdmUgVUlEIFRlc3TTEBESExQZWiRjbGFz
+ c25hbWVYJGNsYXNzZXNbJGNsYXNzaGludHNfEBdPQ19CdWlsdGluUHl0aG9u
+ VW5pY29kZaQVFhcYXxAXT0NfQnVpbHRpblB5dGhvblVuaWNvZGVfEBBPQ19Q
+ eXRob25Vbmljb2RlWE5TU3RyaW5nWE5TT2JqZWN0ohobXxAPT0NfUHl0aG9u
+ U3RyaW5nWE5TU3RyaW5nXxAPTlNLZXllZEFyY2hpdmVy0R4fVHJvb3SAAQAI
+ ABEAGgAjAC0AMgA3ADsAQQBIAE8AVgBgAGIAZAB6AIEAjACVAKEAuwDAANoA
+ 7QD2AP8BAgEUAR0BLwEyATcAAAAAAAACAQAAAAAAAAAgAAAAAAAAAAAAAAAA
+ AAABOQ=='''),
}
with self.subTest(fmt=fmt):
self.assertRaises(TypeError, plistlib.dumps, pl, fmt=fmt)
+ def test_invalid_uid(self):
+ with self.assertRaises(TypeError):
+ UID("not an int")
+ with self.assertRaises(ValueError):
+ UID(2 ** 64)
+ with self.assertRaises(ValueError):
+ UID(-19)
+
def test_int(self):
for pl in [0, 2**8-1, 2**8, 2**16-1, 2**16, 2**32-1, 2**32,
2**63-1, 2**64-1, 1, -2**63]:
data = {'1': {'2': [{'3': [[[[[{'test': b'aaaaaa'}]]]]]}]}}
self.assertEqual(plistlib.loads(plistlib.dumps(data)), data)
+ def test_uid(self):
+ data = UID(1)
+ self.assertEqual(plistlib.loads(plistlib.dumps(data, fmt=plistlib.FMT_BINARY)), data)
+ dict_data = {
+ 'uid0': UID(0),
+ 'uid2': UID(2),
+ 'uid8': UID(2 ** 8),
+ 'uid16': UID(2 ** 16),
+ 'uid32': UID(2 ** 32),
+ 'uid63': UID(2 ** 63)
+ }
+ self.assertEqual(plistlib.loads(plistlib.dumps(dict_data, fmt=plistlib.FMT_BINARY)), dict_data)
+
+ def test_uid_data(self):
+ uid = UID(1)
+ self.assertEqual(uid.data, 1)
+
+ def test_uid_eq(self):
+ self.assertEqual(UID(1), UID(1))
+ self.assertNotEqual(UID(1), UID(2))
+ self.assertNotEqual(UID(1), "not uid")
+
+ def test_uid_hash(self):
+ self.assertEqual(hash(UID(1)), hash(UID(1)))
+
+ def test_uid_repr(self):
+ self.assertEqual(repr(UID(1)), "UID(1)")
+
+ def test_uid_index(self):
+ self.assertEqual(operator.index(UID(1)), 1)
+
+ def test_uid_pickle(self):
+ for proto in range(pickle.HIGHEST_PROTOCOL + 1):
+ self.assertEqual(pickle.loads(pickle.dumps(UID(19), protocol=proto)), UID(19))
+
+ def test_uid_copy(self):
+ self.assertEqual(copy.copy(UID(1)), UID(1))
+ self.assertEqual(copy.deepcopy(UID(1)), UID(1))
+
def test_appleformatting(self):
for use_builtin_types in (True, False):
for fmt in ALL_FORMATS:
self.assertEqual(cur, in_data)
+class TestKeyedArchive(unittest.TestCase):
+ def test_keyed_archive_data(self):
+ # This is the structure of a NSKeyedArchive packed plist
+ data = {
+ '$version': 100000,
+ '$objects': [
+ '$null', {
+ 'pytype': 1,
+ '$class': UID(2),
+ 'NS.string': 'KeyArchive UID Test'
+ },
+ {
+ '$classname': 'OC_BuiltinPythonUnicode',
+ '$classes': [
+ 'OC_BuiltinPythonUnicode',
+ 'OC_PythonUnicode',
+ 'NSString',
+ 'NSObject'
+ ],
+ '$classhints': [
+ 'OC_PythonString', 'NSString'
+ ]
+ }
+ ],
+ '$archiver': 'NSKeyedArchiver',
+ '$top': {
+ 'root': UID(1)
+ }
+ }
+ self.assertEqual(plistlib.loads(TESTDATA["KEYED_ARCHIVE"]), data)
+
+
class MiscTestCase(unittest.TestCase):
def test__all__(self):
blacklist = {"PlistFormat", "PLISTHEADER"}
def test_main():
- support.run_unittest(TestPlistlib, TestPlistlibDeprecated, MiscTestCase)
+ support.run_unittest(TestPlistlib, TestPlistlibDeprecated, TestKeyedArchive, MiscTestCase)
if __name__ == '__main__':