mirror of
https://github.com/python/cpython.git
synced 2025-09-14 04:37:29 +00:00
Which reminds me, I've had a much improved plistlib.py lying around for
ages. The main improvements are: - a much more convenient API: readPlist() and writePlist() - support non-dict top-level objects
This commit is contained in:
parent
d1b3d88bf3
commit
4c3d054d3d
1 changed files with 113 additions and 85 deletions
|
@ -1,15 +1,16 @@
|
||||||
"""plistlib.py -- a tool to generate and parse MacOSX .plist files.
|
"""plistlib.py -- a tool to generate and parse MacOSX .plist files.
|
||||||
|
|
||||||
The main class in this module is Plist. It takes a set of arbitrary
|
The PropertList (.plist) file format is a simple XML pickle supporting
|
||||||
keyword arguments, which will be the top level elements of the plist
|
basic object types, like dictionaries, lists, numbers and strings.
|
||||||
dictionary. After instantiation you can add more elements by assigning
|
Usually the top level object is a dictionary.
|
||||||
new attributes to the Plist instance.
|
|
||||||
|
|
||||||
To write out a plist file, call the write() method of the Plist
|
To write out a plist file, use the writePlist(rootObject, pathOrFile)
|
||||||
instance with a filename or a file object.
|
function. 'rootObject' is the top level object, 'pathOrFile' is a
|
||||||
|
filename or a (writable) file object.
|
||||||
|
|
||||||
To parse a plist from a file, use the Plist.fromFile(pathOrFile)
|
To parse a plist from a file, use the readPlist(pathOrFile)
|
||||||
classmethod, with a file name or a file object as the only argument.
|
function, with a file name or a (readable) file object as the only
|
||||||
|
argument. It returns the top level object (usually a dictionary).
|
||||||
(Warning: you need pyexpat installed for this to work, ie. it doesn't
|
(Warning: you need pyexpat installed for this to work, ie. it doesn't
|
||||||
work with a vanilla Python 2.2 as shipped with MacOS X.2.)
|
work with a vanilla Python 2.2 as shipped with MacOS X.2.)
|
||||||
|
|
||||||
|
@ -17,9 +18,10 @@ Values can be strings, integers, floats, booleans, tuples, lists,
|
||||||
dictionaries, Data or Date objects. String values (including dictionary
|
dictionaries, Data or Date objects. String values (including dictionary
|
||||||
keys) may be unicode strings -- they will be written out as UTF-8.
|
keys) may be unicode strings -- they will be written out as UTF-8.
|
||||||
|
|
||||||
For convenience, this module exports a class named Dict(), which
|
This module exports a class named Dict(), which allows you to easily
|
||||||
allows you to easily construct (nested) dicts using keyword arguments.
|
construct (nested) dicts using keyword arguments as well as accessing
|
||||||
But regular dicts work, too.
|
values with attribute notation, where d.foo is equivalent to d["foo"].
|
||||||
|
Regular dictionaries work, too.
|
||||||
|
|
||||||
To support Boolean values in plists with Python < 2.3, "bool", "True"
|
To support Boolean values in plists with Python < 2.3, "bool", "True"
|
||||||
and "False" are exported. Use these symbols from this module if you
|
and "False" are exported. Use these symbols from this module if you
|
||||||
|
@ -33,7 +35,7 @@ The <date> plist data has (limited) support through the Date class.
|
||||||
|
|
||||||
Generate Plist example:
|
Generate Plist example:
|
||||||
|
|
||||||
pl = Plist(
|
pl = Dict(
|
||||||
aString="Doodah",
|
aString="Doodah",
|
||||||
aList=["A", "B", 12, 32.1, [1, 2, 3]],
|
aList=["A", "B", 12, 32.1, [1, 2, 3]],
|
||||||
aFloat = 0.1,
|
aFloat = 0.1,
|
||||||
|
@ -50,31 +52,62 @@ Generate Plist example:
|
||||||
)
|
)
|
||||||
# unicode keys are possible, but a little awkward to use:
|
# unicode keys are possible, but a little awkward to use:
|
||||||
pl[u'\xc5benraa'] = "That was a unicode key."
|
pl[u'\xc5benraa'] = "That was a unicode key."
|
||||||
pl.write(fileName)
|
writePlist(pl, fileName)
|
||||||
|
|
||||||
Parse Plist example:
|
Parse Plist example:
|
||||||
|
|
||||||
pl = Plist.fromFile(pathOrFile)
|
pl = readPlist(pathOrFile)
|
||||||
print pl.aKey
|
print pl.aKey # same as pl["aKey"]
|
||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# written by Just van Rossum (just@letterror.com), 2002-11-19
|
|
||||||
|
__all__ = ["readPlist", "writePlist", "Plist", "Data", "Date", "Dict",
|
||||||
|
"False", "True", "bool"]
|
||||||
|
# Note: the Plist class has been deprecated, Dict, False, True and bool
|
||||||
|
# are here only for compatibility with Python 2.2.
|
||||||
|
|
||||||
|
|
||||||
__all__ = ["Plist", "Data", "Date", "Dict", "False", "True", "bool"]
|
def readPlist(pathOrFile):
|
||||||
|
"""Read a .plist file. 'pathOrFile' may either be a file name or a
|
||||||
|
(readable) file object. Return the unpacked root object (which
|
||||||
|
usually is a dictionary).
|
||||||
|
"""
|
||||||
|
didOpen = 0
|
||||||
|
if isinstance(pathOrFile, (str, unicode)):
|
||||||
|
pathOrFile = open(pathOrFile)
|
||||||
|
didOpen = 1
|
||||||
|
p = PlistParser()
|
||||||
|
rootObject = p.parse(pathOrFile)
|
||||||
|
if didOpen:
|
||||||
|
pathOrFile.close()
|
||||||
|
return rootObject
|
||||||
|
|
||||||
|
|
||||||
INDENT = "\t"
|
def writePlist(rootObject, pathOrFile):
|
||||||
|
"""Write 'rootObject' to a .plist file. 'pathOrFile' may either be a
|
||||||
|
file name or a (writable) file object.
|
||||||
|
"""
|
||||||
|
didOpen = 0
|
||||||
|
if isinstance(pathOrFile, (str, unicode)):
|
||||||
|
pathOrFile = open(pathOrFile, "w")
|
||||||
|
didOpen = 1
|
||||||
|
writer = PlistWriter(pathOrFile)
|
||||||
|
writer.writeln("<plist version=\"1.0\">")
|
||||||
|
writer.writeValue(rootObject)
|
||||||
|
writer.writeln("</plist>")
|
||||||
|
if didOpen:
|
||||||
|
pathOrFile.close()
|
||||||
|
|
||||||
|
|
||||||
class DumbXMLWriter:
|
class DumbXMLWriter:
|
||||||
|
|
||||||
def __init__(self, file):
|
def __init__(self, file, indentLevel=0, indent="\t"):
|
||||||
self.file = file
|
self.file = file
|
||||||
self.stack = []
|
self.stack = []
|
||||||
self.indentLevel = 0
|
self.indentLevel = indentLevel
|
||||||
|
self.indent = indent
|
||||||
|
|
||||||
def beginElement(self, element):
|
def beginElement(self, element):
|
||||||
self.stack.append(element)
|
self.stack.append(element)
|
||||||
|
@ -89,22 +122,30 @@ 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 = _encode(value)
|
value = _escapeAndEncode(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 * INDENT + line + "\n")
|
self.file.write(self.indentLevel * self.indent + line + "\n")
|
||||||
else:
|
else:
|
||||||
self.file.write("\n")
|
self.file.write("\n")
|
||||||
|
|
||||||
|
|
||||||
def _encode(text):
|
import re
|
||||||
text = text.replace("&", "&")
|
# Regex to strip all control chars, but for \t \n \r and \f
|
||||||
text = text.replace("<", "<")
|
_controlStripper = re.compile(r"[\x00\x01\x02\x03\x04\x05\x06\x07\x08\x0b\x0e\x0f"
|
||||||
return text.encode("utf-8")
|
"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f]")
|
||||||
|
|
||||||
|
def _escapeAndEncode(text):
|
||||||
|
text = text.replace("\r\n", "\n") # convert DOS line endings
|
||||||
|
text = text.replace("\r", "\n") # convert Mac line endings
|
||||||
|
text = text.replace("&", "&") # escape '&'
|
||||||
|
text = text.replace("<", "<") # escape '<'
|
||||||
|
text = _controlStripper.sub("?", text) # replace control chars with '?'
|
||||||
|
return text.encode("utf-8") # encode as UTF-8
|
||||||
|
|
||||||
|
|
||||||
PLISTHEADER = """\
|
PLISTHEADER = """\
|
||||||
|
@ -114,9 +155,10 @@ PLISTHEADER = """\
|
||||||
|
|
||||||
class PlistWriter(DumbXMLWriter):
|
class PlistWriter(DumbXMLWriter):
|
||||||
|
|
||||||
def __init__(self, file):
|
def __init__(self, file, indentLevel=0, indent="\t", writeHeader=1):
|
||||||
file.write(PLISTHEADER)
|
if writeHeader:
|
||||||
DumbXMLWriter.__init__(self, file)
|
file.write(PLISTHEADER)
|
||||||
|
DumbXMLWriter.__init__(self, file, indentLevel, indent)
|
||||||
|
|
||||||
def writeValue(self, value):
|
def writeValue(self, value):
|
||||||
if isinstance(value, (str, unicode)):
|
if isinstance(value, (str, unicode)):
|
||||||
|
@ -133,7 +175,7 @@ class PlistWriter(DumbXMLWriter):
|
||||||
elif isinstance(value, float):
|
elif isinstance(value, float):
|
||||||
# should perhaps use repr() for better precision?
|
# should perhaps use repr() for better precision?
|
||||||
self.simpleElement("real", str(value))
|
self.simpleElement("real", str(value))
|
||||||
elif isinstance(value, (dict, Dict)):
|
elif isinstance(value, dict):
|
||||||
self.writeDict(value)
|
self.writeDict(value)
|
||||||
elif isinstance(value, Data):
|
elif isinstance(value, Data):
|
||||||
self.writeData(value)
|
self.writeData(value)
|
||||||
|
@ -169,66 +211,55 @@ class PlistWriter(DumbXMLWriter):
|
||||||
self.endElement("array")
|
self.endElement("array")
|
||||||
|
|
||||||
|
|
||||||
class Dict:
|
class Dict(dict):
|
||||||
|
|
||||||
"""Dict wrapper for convenient access of values through attributes."""
|
"""Convenience dictionary subclass: it allows dict construction using
|
||||||
|
keyword arguments (just like dict() in 2.3) as well as attribute notation
|
||||||
|
to retrieve values, making d.foo more or less uquivalent to d["foo"].
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __new__(cls, **kwargs):
|
||||||
|
self = dict.__new__(cls)
|
||||||
|
self.update(kwargs)
|
||||||
|
return self
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
self.__dict__.update(kwargs)
|
self.update(kwargs)
|
||||||
|
|
||||||
def __cmp__(self, other):
|
|
||||||
if isinstance(other, self.__class__):
|
|
||||||
return cmp(self.__dict__, other.__dict__)
|
|
||||||
elif isinstance(other, dict):
|
|
||||||
return cmp(self.__dict__, other)
|
|
||||||
else:
|
|
||||||
return cmp(id(self), id(other))
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return "%s(**%s)" % (self.__class__.__name__, self.__dict__)
|
|
||||||
__repr__ = __str__
|
|
||||||
|
|
||||||
def copy(self):
|
|
||||||
return self.__class__(**self.__dict__)
|
|
||||||
|
|
||||||
def __getattr__(self, attr):
|
def __getattr__(self, attr):
|
||||||
"""Delegate everything else to the dict object."""
|
try:
|
||||||
return getattr(self.__dict__, attr)
|
value = self[attr]
|
||||||
|
except KeyError:
|
||||||
|
raise AttributeError, attr
|
||||||
|
return value
|
||||||
|
|
||||||
|
def __setattr__(self, attr, value):
|
||||||
|
self[attr] = value
|
||||||
|
|
||||||
|
def __delattr__(self, attr):
|
||||||
|
try:
|
||||||
|
del self[attr]
|
||||||
|
except KeyError:
|
||||||
|
raise AttributeError, attr
|
||||||
|
|
||||||
|
|
||||||
class Plist(Dict):
|
class Plist(Dict):
|
||||||
|
|
||||||
"""The main Plist object. Basically a dict (the toplevel object
|
"""This class has been deprecated! Use the Dict with readPlist() and
|
||||||
of a plist is a dict) with two additional methods to read from
|
writePlist() functions instead.
|
||||||
and write to files.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def fromFile(cls, pathOrFile):
|
def fromFile(cls, pathOrFile):
|
||||||
didOpen = 0
|
"""Deprecated! Use the readPlist() function instead."""
|
||||||
if isinstance(pathOrFile, (str, unicode)):
|
rootObject = readPlist(pathOrFile)
|
||||||
pathOrFile = open(pathOrFile)
|
plist = cls()
|
||||||
didOpen = 1
|
plist.update(rootObject)
|
||||||
p = PlistParser()
|
|
||||||
plist = p.parse(pathOrFile)
|
|
||||||
if didOpen:
|
|
||||||
pathOrFile.close()
|
|
||||||
return plist
|
return plist
|
||||||
fromFile = classmethod(fromFile)
|
fromFile = classmethod(fromFile)
|
||||||
|
|
||||||
def write(self, pathOrFile):
|
def write(self, pathOrFile):
|
||||||
if isinstance(pathOrFile, (str, unicode)):
|
"""Deprecated! Use the writePlist() function instead."""
|
||||||
pathOrFile = open(pathOrFile, "w")
|
writePlist(self, pathOrFile)
|
||||||
didOpen = 1
|
|
||||||
else:
|
|
||||||
didOpen = 0
|
|
||||||
|
|
||||||
writer = PlistWriter(pathOrFile)
|
|
||||||
writer.writeln("<plist version=\"1.0\">")
|
|
||||||
writer.writeDict(self.__dict__)
|
|
||||||
writer.writeln("</plist>")
|
|
||||||
|
|
||||||
if didOpen:
|
|
||||||
pathOrFile.close()
|
|
||||||
|
|
||||||
|
|
||||||
class Data:
|
class Data:
|
||||||
|
@ -323,7 +354,7 @@ class PlistParser:
|
||||||
self.currentKey = None
|
self.currentKey = None
|
||||||
elif not self.stack:
|
elif not self.stack:
|
||||||
# this is the root object
|
# this is the root object
|
||||||
assert self.root is value
|
self.root = value
|
||||||
else:
|
else:
|
||||||
self.stack[-1].append(value)
|
self.stack[-1].append(value)
|
||||||
|
|
||||||
|
@ -339,10 +370,7 @@ class PlistParser:
|
||||||
# element handlers
|
# element handlers
|
||||||
|
|
||||||
def begin_dict(self, attrs):
|
def begin_dict(self, attrs):
|
||||||
if self.root is None:
|
d = Dict()
|
||||||
self.root = d = Plist()
|
|
||||||
else:
|
|
||||||
d = Dict()
|
|
||||||
self.addObject(d)
|
self.addObject(d)
|
||||||
self.stack.append(d)
|
self.stack.append(d)
|
||||||
def end_dict(self):
|
def end_dict(self):
|
||||||
|
@ -401,7 +429,7 @@ if __name__ == "__main__":
|
||||||
from StringIO import StringIO
|
from StringIO import StringIO
|
||||||
import time
|
import time
|
||||||
if len(sys.argv) == 1:
|
if len(sys.argv) == 1:
|
||||||
pl = Plist(
|
pl = Dict(
|
||||||
aString="Doodah",
|
aString="Doodah",
|
||||||
aList=["A", "B", 12, 32.1, [1, 2, 3]],
|
aList=["A", "B", 12, 32.1, [1, 2, 3]],
|
||||||
aFloat = 0.1,
|
aFloat = 0.1,
|
||||||
|
@ -414,10 +442,10 @@ if __name__ == "__main__":
|
||||||
),
|
),
|
||||||
someData = Data("<binary gunk>"),
|
someData = Data("<binary gunk>"),
|
||||||
someMoreData = Data("<lots of binary gunk>" * 10),
|
someMoreData = Data("<lots of binary gunk>" * 10),
|
||||||
aDate = Date(time.mktime(time.gmtime())),
|
# aDate = Date(time.mktime(time.gmtime())),
|
||||||
)
|
)
|
||||||
elif len(sys.argv) == 2:
|
elif len(sys.argv) == 2:
|
||||||
pl = Plist.fromFile(sys.argv[1])
|
pl = readPlist(sys.argv[1])
|
||||||
else:
|
else:
|
||||||
print "Too many arguments: at most 1 plist file can be given."
|
print "Too many arguments: at most 1 plist file can be given."
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
@ -425,13 +453,13 @@ if __name__ == "__main__":
|
||||||
# unicode keys are possible, but a little awkward to use:
|
# unicode keys are possible, but a little awkward to use:
|
||||||
pl[u'\xc5benraa'] = "That was a unicode key."
|
pl[u'\xc5benraa'] = "That was a unicode key."
|
||||||
f = StringIO()
|
f = StringIO()
|
||||||
pl.write(f)
|
writePlist(pl, f)
|
||||||
xml = f.getvalue()
|
xml = f.getvalue()
|
||||||
print xml
|
print xml
|
||||||
f.seek(0)
|
f.seek(0)
|
||||||
pl2 = Plist.fromFile(f)
|
pl2 = readPlist(f)
|
||||||
assert pl == pl2
|
assert pl == pl2
|
||||||
f = StringIO()
|
f = StringIO()
|
||||||
pl2.write(f)
|
writePlist(pl2, f)
|
||||||
assert xml == f.getvalue()
|
assert xml == f.getvalue()
|
||||||
#print repr(pl2)
|
#print repr(pl2)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue