SF patch# 1769016 by James Brotchie.

Change plistlib to use bytes instead of strings.
Fix test_plistlib accordingly.
This commit is contained in:
Guido van Rossum 2007-08-07 14:26:40 +00:00
parent ca8dd9182e
commit cd869d8d41
2 changed files with 45 additions and 53 deletions

View File

@ -12,8 +12,8 @@ To parse a plist from a file, use the readPlist(pathOrFile) function,
with a file name or a (readable) file object as the only argument. It with a file name or a (readable) file object as the only argument. It
returns the top level object (again, usually a dictionary). returns the top level object (again, usually a dictionary).
To work with plist data in strings, you can use readPlistFromString() To work with plist data in bytes objects, you can use readPlistFromBytes()
and writePlistToString(). and writePlistToBytes().
Values can be strings, integers, floats, booleans, tuples, lists, Values can be strings, integers, floats, booleans, tuples, lists,
dictionaries, Data or datetime.datetime objects. String values (including dictionaries, Data or datetime.datetime objects. String values (including
@ -21,7 +21,7 @@ dictionary keys) may be unicode strings -- they will be written out as
UTF-8. UTF-8.
The <data> plist type is supported through the Data class. This is a The <data> plist type is supported through the Data class. This is a
thin wrapper around a Python string. thin wrapper around a Python bytes object.
Generate Plist example: Generate Plist example:
@ -36,8 +36,8 @@ Generate Plist example:
aTrueValue=True, aTrueValue=True,
aFalseValue=False, aFalseValue=False,
), ),
someData = Data("<binary gunk>"), someData = Data(b"<binary gunk>"),
someMoreData = Data("<lots of binary gunk>" * 10), someMoreData = Data(b"<lots of binary gunk>" * 10),
aDate = datetime.datetime.fromtimestamp(time.mktime(time.gmtime())), aDate = datetime.datetime.fromtimestamp(time.mktime(time.gmtime())),
) )
# unicode keys are possible, but a little awkward to use: # unicode keys are possible, but a little awkward to use:
@ -52,7 +52,7 @@ Parse Plist example:
__all__ = [ __all__ = [
"readPlist", "writePlist", "readPlistFromString", "writePlistToString", "readPlist", "writePlist", "readPlistFromBytes", "writePlistToBytes",
"readPlistFromResource", "writePlistToResource", "readPlistFromResource", "writePlistToResource",
"Plist", "Data", "Dict" "Plist", "Data", "Dict"
] ]
@ -60,7 +60,7 @@ __all__ = [
import binascii import binascii
import datetime import datetime
from cStringIO import StringIO from io import BytesIO
import re import re
@ -71,7 +71,7 @@ def readPlist(pathOrFile):
""" """
didOpen = 0 didOpen = 0
if isinstance(pathOrFile, str): if isinstance(pathOrFile, str):
pathOrFile = open(pathOrFile) pathOrFile = open(pathOrFile, 'rb')
didOpen = 1 didOpen = 1
p = PlistParser() p = PlistParser()
rootObject = p.parse(pathOrFile) rootObject = p.parse(pathOrFile)
@ -86,7 +86,7 @@ def writePlist(rootObject, pathOrFile):
""" """
didOpen = 0 didOpen = 0
if isinstance(pathOrFile, str): if isinstance(pathOrFile, str):
pathOrFile = open(pathOrFile, "w") pathOrFile = open(pathOrFile, 'wb')
didOpen = 1 didOpen = 1
writer = PlistWriter(pathOrFile) writer = PlistWriter(pathOrFile)
writer.writeln("<plist version=\"1.0\">") writer.writeln("<plist version=\"1.0\">")
@ -96,16 +96,16 @@ def writePlist(rootObject, pathOrFile):
pathOrFile.close() pathOrFile.close()
def readPlistFromString(data): def readPlistFromBytes(data):
"""Read a plist data from a string. Return the root object. """Read a plist data from a bytes object. Return the root object.
""" """
return readPlist(StringIO(data)) return readPlist(BytesIO(data))
def writePlistToString(rootObject): def writePlistToBytes(rootObject):
"""Return 'rootObject' as a plist-formatted string. """Return 'rootObject' as a plist-formatted bytes object.
""" """
f = StringIO() f = BytesIO()
writePlist(rootObject, f) writePlist(rootObject, f)
return f.getvalue() return f.getvalue()
@ -145,7 +145,6 @@ def writePlistToResource(rootObject, path, restype='plst', resid=0):
class DumbXMLWriter: class DumbXMLWriter:
def __init__(self, file, indentLevel=0, indent="\t"): def __init__(self, file, indentLevel=0, indent="\t"):
self.file = file self.file = file
self.stack = [] self.stack = []
@ -172,9 +171,12 @@ class DumbXMLWriter:
def writeln(self, line): def writeln(self, line):
if line: if line:
self.file.write(self.indentLevel * self.indent + line + "\n") # plist has fixed encoding of utf-8
else: if isinstance(line, str):
self.file.write("\n") line = line.encode('utf-8')
self.file.write(self.indentLevel * self.indent)
self.file.write(line)
self.file.write('\n')
# Contents should conform to a subset of ISO 8601 # Contents should conform to a subset of ISO 8601
@ -355,13 +357,15 @@ def _encodeBase64(s, maxlinelength=76):
for i in range(0, len(s), maxbinsize): for i in range(0, len(s), maxbinsize):
chunk = s[i : i + maxbinsize] chunk = s[i : i + maxbinsize]
pieces.append(binascii.b2a_base64(chunk)) pieces.append(binascii.b2a_base64(chunk))
return "".join(pieces) return b''.join(pieces)
class Data: class Data:
"""Wrapper for binary data.""" """Wrapper for binary data."""
def __init__(self, data): def __init__(self, data):
if not isinstance(data, bytes):
raise TypeError("data must be as bytes")
self.data = data self.data = data
def fromBase64(cls, data): def fromBase64(cls, data):
@ -426,11 +430,7 @@ class PlistParser:
self.stack[-1].append(value) self.stack[-1].append(value)
def getData(self): def getData(self):
data = "".join(self.data) data = ''.join(self.data)
try:
data = data.encode("ascii")
except UnicodeError:
pass
self.data = [] self.data = []
return data return data

View File

@ -9,7 +9,7 @@ from test import test_support
# This test data was generated through Cocoa's NSDictionary class # This test data was generated through Cocoa's NSDictionary class
TESTDATA = """<?xml version="1.0" encoding="UTF-8"?> TESTDATA = b"""<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" \ <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" \
"http://www.apple.com/DTDs/PropertyList-1.0.dtd"> "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
@ -109,9 +109,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("<binary gunk>"), someData = plistlib.Data(b"<binary gunk>"),
someMoreData = plistlib.Data("<lots of binary gunk>\0\1\2\3" * 10), someMoreData = plistlib.Data(b"<lots of binary gunk>\0\1\2\3" * 10),
nestedData = [plistlib.Data("<lots of binary gunk>\0\1\2\3" * 10)], nestedData = [plistlib.Data(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),
) )
pl['\xc5benraa'] = "That was a unicode key." pl['\xc5benraa'] = "That was a unicode key."
@ -128,40 +128,32 @@ class TestPlistlib(unittest.TestCase):
pl2 = plistlib.readPlist(test_support.TESTFN) pl2 = plistlib.readPlist(test_support.TESTFN)
self.assertEqual(dict(pl), dict(pl2)) self.assertEqual(dict(pl), dict(pl2))
def test_string(self): def test_bytes(self):
pl = self._create() pl = self._create()
data = plistlib.writePlistToString(pl) data = plistlib.writePlistToBytes(pl)
pl2 = plistlib.readPlistFromString(data) pl2 = plistlib.readPlistFromBytes(data)
self.assertEqual(dict(pl), dict(pl2)) self.assertEqual(dict(pl), dict(pl2))
data2 = plistlib.writePlistToString(pl2) data2 = plistlib.writePlistToBytes(pl2)
self.assertEqual(data, data2) self.assertEqual(data, data2)
def test_appleformatting(self): def test_appleformatting(self):
pl = plistlib.readPlistFromString(TESTDATA) pl = plistlib.readPlistFromBytes(TESTDATA)
data = plistlib.writePlistToString(pl) data = plistlib.writePlistToBytes(pl)
self.assertEqual(data, TESTDATA, self.assertEqual(data, TESTDATA,
"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() pl = self._create()
pl2 = plistlib.readPlistFromString(TESTDATA) pl2 = plistlib.readPlistFromBytes(TESTDATA)
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_stringio(self): def test_bytesio(self):
from StringIO import StringIO from io import BytesIO
f = StringIO() b = BytesIO()
pl = self._create() pl = self._create()
plistlib.writePlist(pl, f) plistlib.writePlist(pl, b)
pl2 = plistlib.readPlist(StringIO(f.getvalue())) pl2 = plistlib.readPlist(BytesIO(b.getvalue()))
self.assertEqual(dict(pl), dict(pl2))
def test_cstringio(self):
from cStringIO import StringIO
f = StringIO()
pl = self._create()
plistlib.writePlist(pl, f)
pl2 = plistlib.readPlist(StringIO(f.getvalue()))
self.assertEqual(dict(pl), dict(pl2)) self.assertEqual(dict(pl), dict(pl2))
def test_controlcharacters(self): def test_controlcharacters(self):
@ -170,17 +162,17 @@ class TestPlistlib(unittest.TestCase):
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.writePlistToString(testString) plistlib.writePlistToBytes(testString)
else: else:
self.assertRaises(ValueError, self.assertRaises(ValueError,
plistlib.writePlistToString, plistlib.writePlistToBytes,
testString) testString)
def test_nondictroot(self): def test_nondictroot(self):
test1 = "abc" test1 = "abc"
test2 = [1, 2, 3, "abc"] test2 = [1, 2, 3, "abc"]
result1 = plistlib.readPlistFromString(plistlib.writePlistToString(test1)) result1 = plistlib.readPlistFromBytes(plistlib.writePlistToBytes(test1))
result2 = plistlib.readPlistFromString(plistlib.writePlistToString(test2)) result2 = plistlib.readPlistFromBytes(plistlib.writePlistToBytes(test2))
self.assertEqual(test1, result1) self.assertEqual(test1, result1)
self.assertEqual(test2, result2) self.assertEqual(test2, result2)