Copied plistlib.py from r60150 Lib/plat-mac/plistlib.py to Lib/

This commit is contained in:
Christian Heimes 2008-01-26 22:09:42 +00:00
parent 6ff93fee5f
commit 4f110d8805

View file

@ -1,6 +1,6 @@
"""plistlib.py -- a tool to generate and parse MacOSX .plist files. """plistlib.py -- a tool to generate and parse MacOSX .plist files.
The PropertyList (.plist) file format is a simple XML pickle supporting The PropertList (.plist) file format is a simple XML pickle supporting
basic object types, like dictionaries, lists, numbers and strings. basic object types, like dictionaries, lists, numbers and strings.
Usually the top level object is a dictionary. Usually the top level object is a dictionary.
@ -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 io import StringIO from io import BytesIO
import re import re
@ -69,10 +69,10 @@ def readPlist(pathOrFile):
(readable) file object. Return the unpacked root object (which (readable) file object. Return the unpacked root object (which
usually is a dictionary). usually is a dictionary).
""" """
didOpen = 0 didOpen = False
if isinstance(pathOrFile, str): if isinstance(pathOrFile, str):
pathOrFile = open(pathOrFile) pathOrFile = open(pathOrFile, 'rb')
didOpen = 1 didOpen = True
p = PlistParser() p = PlistParser()
rootObject = p.parse(pathOrFile) rootObject = p.parse(pathOrFile)
if didOpen: if didOpen:
@ -84,10 +84,10 @@ def writePlist(rootObject, pathOrFile):
"""Write 'rootObject' to a .plist file. 'pathOrFile' may either be a """Write 'rootObject' to a .plist file. 'pathOrFile' may either be a
file name or a (writable) file object. file name or a (writable) file object.
""" """
didOpen = 0 didOpen = False
if isinstance(pathOrFile, str): if isinstance(pathOrFile, str):
pathOrFile = open(pathOrFile, "w") pathOrFile = open(pathOrFile, 'wb')
didOpen = 1 didOpen = True
writer = PlistWriter(pathOrFile) writer = PlistWriter(pathOrFile)
writer.writeln("<plist version=\"1.0\">") writer.writeln("<plist version=\"1.0\">")
writer.writeValue(rootObject) writer.writeValue(rootObject)
@ -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 = []
@ -165,16 +164,19 @@ class DumbXMLWriter:
def simpleElement(self, element, value=None): def simpleElement(self, element, value=None):
if value is not None: if value is not None:
value = _escapeAndEncode(value) value = _escape(value)
self.writeln("<%s>%s</%s>" % (element, value, element)) self.writeln("<%s>%s</%s>" % (element, value, element))
else: else:
self.writeln("<%s/>" % element) self.writeln("<%s/>" % element)
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(b'\n')
# Contents should conform to a subset of ISO 8601 # Contents should conform to a subset of ISO 8601
@ -205,7 +207,7 @@ _controlCharPat = re.compile(
r"[\x00\x01\x02\x03\x04\x05\x06\x07\x08\x0b\x0c\x0e\x0f" r"[\x00\x01\x02\x03\x04\x05\x06\x07\x08\x0b\x0c\x0e\x0f"
r"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f]") r"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f]")
def _escapeAndEncode(text): def _escape(text):
m = _controlCharPat.search(text) m = _controlCharPat.search(text)
if m is not None: if m is not None:
raise ValueError("strings can't contains control characters; " raise ValueError("strings can't contains control characters; "
@ -215,17 +217,17 @@ def _escapeAndEncode(text):
text = text.replace("&", "&amp;") # escape '&' text = text.replace("&", "&amp;") # escape '&'
text = text.replace("<", "&lt;") # escape '<' text = text.replace("<", "&lt;") # escape '<'
text = text.replace(">", "&gt;") # escape '>' text = text.replace(">", "&gt;") # escape '>'
return text.encode("utf-8") # encode as UTF-8 return text
PLISTHEADER = """\ PLISTHEADER = b"""\
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
""" """
class PlistWriter(DumbXMLWriter): class PlistWriter(DumbXMLWriter):
def __init__(self, file, indentLevel=0, indent="\t", writeHeader=1): def __init__(self, file, indentLevel=0, indent=b"\t", writeHeader=1):
if writeHeader: if writeHeader:
file.write(PLISTHEADER) file.write(PLISTHEADER)
DumbXMLWriter.__init__(self, file, indentLevel, indent) DumbXMLWriter.__init__(self, file, indentLevel, indent)
@ -258,9 +260,9 @@ class PlistWriter(DumbXMLWriter):
def writeData(self, data): def writeData(self, data):
self.beginElement("data") self.beginElement("data")
self.indentLevel -= 1 self.indentLevel -= 1
maxlinelength = 76 - len(self.indent.replace("\t", " " * 8) * maxlinelength = 76 - len(self.indent.replace(b"\t", b" " * 8) *
self.indentLevel) self.indentLevel)
for line in data.asBase64(maxlinelength).split("\n"): for line in data.asBase64(maxlinelength).split(b"\n"):
if line: if line:
self.writeln(line) self.writeln(line)
self.indentLevel += 1 self.indentLevel += 1
@ -268,8 +270,7 @@ class PlistWriter(DumbXMLWriter):
def writeDict(self, d): def writeDict(self, d):
self.beginElement("dict") self.beginElement("dict")
items = list(d.items()) items = sorted(d.items())
items.sort()
for key, value in items: for key, value in items:
if not isinstance(key, str): if not isinstance(key, str):
raise TypeError("keys must be strings") raise TypeError("keys must be strings")
@ -321,7 +322,7 @@ class Dict(_InternalDict):
from warnings import warn from warnings import warn
warn("The plistlib.Dict class is deprecated, use builtin dict instead", warn("The plistlib.Dict class is deprecated, use builtin dict instead",
PendingDeprecationWarning) PendingDeprecationWarning)
super(Dict, self).__init__(**kwargs) super().__init__(**kwargs)
class Plist(_InternalDict): class Plist(_InternalDict):
@ -334,7 +335,7 @@ class Plist(_InternalDict):
from warnings import warn from warnings import warn
warn("The Plist class is deprecated, use the readPlist() and " warn("The Plist class is deprecated, use the readPlist() and "
"writePlist() functions instead", PendingDeprecationWarning) "writePlist() functions instead", PendingDeprecationWarning)
super(Plist, self).__init__(**kwargs) super().__init__(**kwargs)
def fromFile(cls, pathOrFile): def fromFile(cls, pathOrFile):
"""Deprecated. Use the readPlist() function instead.""" """Deprecated. Use the readPlist() function instead."""
@ -356,31 +357,33 @@ 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
@classmethod
def fromBase64(cls, data): def fromBase64(cls, data):
# base64.decodestring just calls binascii.a2b_base64; # base64.decodestring just calls binascii.a2b_base64;
# it seems overkill to use both base64 and binascii. # it seems overkill to use both base64 and binascii.
return cls(binascii.a2b_base64(data)) return cls(binascii.a2b_base64(data))
fromBase64 = classmethod(fromBase64)
def asBase64(self, maxlinelength=76): def asBase64(self, maxlinelength=76):
return _encodeBase64(self.data, maxlinelength) return _encodeBase64(self.data, maxlinelength)
def __cmp__(self, other): def __eq__(self, other):
if isinstance(other, self.__class__): if isinstance(other, self.__class__):
return cmp(self.data, other.data) return self.data == other.data
elif isinstance(other, str): elif isinstance(other, str):
return cmp(self.data, other) return self.data == other
else: else:
return cmp(id(self), id(other)) return id(self) == id(other)
def __repr__(self): def __repr__(self):
return "%s(%s)" % (self.__class__.__name__, repr(self.data)) return "%s(%s)" % (self.__class__.__name__, repr(self.data))
@ -427,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
@ -465,6 +464,6 @@ class PlistParser:
def end_string(self): def end_string(self):
self.addObject(self.getData()) self.addObject(self.getData())
def end_data(self): def end_data(self):
self.addObject(Data.fromBase64(self.getData())) self.addObject(Data.fromBase64(self.getData().encode("utf-8")))
def end_date(self): def end_date(self):
self.addObject(_dateFromString(self.getData())) self.addObject(_dateFromString(self.getData()))