Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 4c3d054

Browse files
committed
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
1 parent d1b3d88 commit 4c3d054

1 file changed

Lines changed: 113 additions & 85 deletions

File tree

Lib/plat-mac/plistlib.py

Lines changed: 113 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,27 @@
11
"""plistlib.py -- a tool to generate and parse MacOSX .plist files.
22
3-
The main class in this module is Plist. It takes a set of arbitrary
4-
keyword arguments, which will be the top level elements of the plist
5-
dictionary. After instantiation you can add more elements by assigning
6-
new attributes to the Plist instance.
3+
The PropertList (.plist) file format is a simple XML pickle supporting
4+
basic object types, like dictionaries, lists, numbers and strings.
5+
Usually the top level object is a dictionary.
76
8-
To write out a plist file, call the write() method of the Plist
9-
instance with a filename or a file object.
7+
To write out a plist file, use the writePlist(rootObject, pathOrFile)
8+
function. 'rootObject' is the top level object, 'pathOrFile' is a
9+
filename or a (writable) file object.
1010
11-
To parse a plist from a file, use the Plist.fromFile(pathOrFile)
12-
classmethod, with a file name or a file object as the only argument.
11+
To parse a plist from a file, use the readPlist(pathOrFile)
12+
function, with a file name or a (readable) file object as the only
13+
argument. It returns the top level object (usually a dictionary).
1314
(Warning: you need pyexpat installed for this to work, ie. it doesn't
1415
work with a vanilla Python 2.2 as shipped with MacOS X.2.)
1516
1617
Values can be strings, integers, floats, booleans, tuples, lists,
1718
dictionaries, Data or Date objects. String values (including dictionary
1819
keys) may be unicode strings -- they will be written out as UTF-8.
1920
20-
For convenience, this module exports a class named Dict(), which
21-
allows you to easily construct (nested) dicts using keyword arguments.
22-
But regular dicts work, too.
21+
This module exports a class named Dict(), which allows you to easily
22+
construct (nested) dicts using keyword arguments as well as accessing
23+
values with attribute notation, where d.foo is equivalent to d["foo"].
24+
Regular dictionaries work, too.
2325
2426
To support Boolean values in plists with Python < 2.3, "bool", "True"
2527
and "False" are exported. Use these symbols from this module if you
@@ -33,7 +35,7 @@
3335
3436
Generate Plist example:
3537
36-
pl = Plist(
38+
pl = Dict(
3739
aString="Doodah",
3840
aList=["A", "B", 12, 32.1, [1, 2, 3]],
3941
aFloat = 0.1,
@@ -50,31 +52,62 @@
5052
)
5153
# unicode keys are possible, but a little awkward to use:
5254
pl[u'\xc5benraa'] = "That was a unicode key."
53-
pl.write(fileName)
55+
writePlist(pl, fileName)
5456
5557
Parse Plist example:
5658
57-
pl = Plist.fromFile(pathOrFile)
58-
print pl.aKey
59+
pl = readPlist(pathOrFile)
60+
print pl.aKey # same as pl["aKey"]
5961
6062
6163
"""
6264

63-
# written by Just van Rossum ([email protected]), 2002-11-19
6465

66+
__all__ = ["readPlist", "writePlist", "Plist", "Data", "Date", "Dict",
67+
"False", "True", "bool"]
68+
# Note: the Plist class has been deprecated, Dict, False, True and bool
69+
# are here only for compatibility with Python 2.2.
6570

66-
__all__ = ["Plist", "Data", "Date", "Dict", "False", "True", "bool"]
6771

68-
69-
INDENT = "\t"
72+
def readPlist(pathOrFile):
73+
"""Read a .plist file. 'pathOrFile' may either be a file name or a
74+
(readable) file object. Return the unpacked root object (which
75+
usually is a dictionary).
76+
"""
77+
didOpen = 0
78+
if isinstance(pathOrFile, (str, unicode)):
79+
pathOrFile = open(pathOrFile)
80+
didOpen = 1
81+
p = PlistParser()
82+
rootObject = p.parse(pathOrFile)
83+
if didOpen:
84+
pathOrFile.close()
85+
return rootObject
86+
87+
88+
def writePlist(rootObject, pathOrFile):
89+
"""Write 'rootObject' to a .plist file. 'pathOrFile' may either be a
90+
file name or a (writable) file object.
91+
"""
92+
didOpen = 0
93+
if isinstance(pathOrFile, (str, unicode)):
94+
pathOrFile = open(pathOrFile, "w")
95+
didOpen = 1
96+
writer = PlistWriter(pathOrFile)
97+
writer.writeln("<plist version=\"1.0\">")
98+
writer.writeValue(rootObject)
99+
writer.writeln("</plist>")
100+
if didOpen:
101+
pathOrFile.close()
70102

71103

72104
class DumbXMLWriter:
73105

74-
def __init__(self, file):
106+
def __init__(self, file, indentLevel=0, indent="\t"):
75107
self.file = file
76108
self.stack = []
77-
self.indentLevel = 0
109+
self.indentLevel = indentLevel
110+
self.indent = indent
78111

79112
def beginElement(self, element):
80113
self.stack.append(element)
@@ -89,22 +122,30 @@ def endElement(self, element):
89122

90123
def simpleElement(self, element, value=None):
91124
if value is not None:
92-
value = _encode(value)
125+
value = _escapeAndEncode(value)
93126
self.writeln("<%s>%s</%s>" % (element, value, element))
94127
else:
95128
self.writeln("<%s/>" % element)
96129

97130
def writeln(self, line):
98131
if line:
99-
self.file.write(self.indentLevel * INDENT + line + "\n")
132+
self.file.write(self.indentLevel * self.indent + line + "\n")
100133
else:
101134
self.file.write("\n")
102135

103136

104-
def _encode(text):
105-
text = text.replace("&", "&amp;")
106-
text = text.replace("<", "&lt;")
107-
return text.encode("utf-8")
137+
import re
138+
# Regex to strip all control chars, but for \t \n \r and \f
139+
_controlStripper = re.compile(r"[\x00\x01\x02\x03\x04\x05\x06\x07\x08\x0b\x0e\x0f"
140+
"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f]")
141+
142+
def _escapeAndEncode(text):
143+
text = text.replace("\r\n", "\n") # convert DOS line endings
144+
text = text.replace("\r", "\n") # convert Mac line endings
145+
text = text.replace("&", "&amp;") # escape '&'
146+
text = text.replace("<", "&lt;") # escape '<'
147+
text = _controlStripper.sub("?", text) # replace control chars with '?'
148+
return text.encode("utf-8") # encode as UTF-8
108149

109150

110151
PLISTHEADER = """\
@@ -114,9 +155,10 @@ def _encode(text):
114155

115156
class PlistWriter(DumbXMLWriter):
116157

117-
def __init__(self, file):
118-
file.write(PLISTHEADER)
119-
DumbXMLWriter.__init__(self, file)
158+
def __init__(self, file, indentLevel=0, indent="\t", writeHeader=1):
159+
if writeHeader:
160+
file.write(PLISTHEADER)
161+
DumbXMLWriter.__init__(self, file, indentLevel, indent)
120162

121163
def writeValue(self, value):
122164
if isinstance(value, (str, unicode)):
@@ -133,7 +175,7 @@ def writeValue(self, value):
133175
elif isinstance(value, float):
134176
# should perhaps use repr() for better precision?
135177
self.simpleElement("real", str(value))
136-
elif isinstance(value, (dict, Dict)):
178+
elif isinstance(value, dict):
137179
self.writeDict(value)
138180
elif isinstance(value, Data):
139181
self.writeData(value)
@@ -169,66 +211,55 @@ def writeArray(self, array):
169211
self.endElement("array")
170212

171213

172-
class Dict:
214+
class Dict(dict):
173215

174-
"""Dict wrapper for convenient access of values through attributes."""
216+
"""Convenience dictionary subclass: it allows dict construction using
217+
keyword arguments (just like dict() in 2.3) as well as attribute notation
218+
to retrieve values, making d.foo more or less uquivalent to d["foo"].
219+
"""
175220

176-
def __init__(self, **kwargs):
177-
self.__dict__.update(kwargs)
221+
def __new__(cls, **kwargs):
222+
self = dict.__new__(cls)
223+
self.update(kwargs)
224+
return self
178225

179-
def __cmp__(self, other):
180-
if isinstance(other, self.__class__):
181-
return cmp(self.__dict__, other.__dict__)
182-
elif isinstance(other, dict):
183-
return cmp(self.__dict__, other)
184-
else:
185-
return cmp(id(self), id(other))
226+
def __init__(self, **kwargs):
227+
self.update(kwargs)
186228

187-
def __str__(self):
188-
return "%s(**%s)" % (self.__class__.__name__, self.__dict__)
189-
__repr__ = __str__
229+
def __getattr__(self, attr):
230+
try:
231+
value = self[attr]
232+
except KeyError:
233+
raise AttributeError, attr
234+
return value
190235

191-
def copy(self):
192-
return self.__class__(**self.__dict__)
236+
def __setattr__(self, attr, value):
237+
self[attr] = value
193238

194-
def __getattr__(self, attr):
195-
"""Delegate everything else to the dict object."""
196-
return getattr(self.__dict__, attr)
239+
def __delattr__(self, attr):
240+
try:
241+
del self[attr]
242+
except KeyError:
243+
raise AttributeError, attr
197244

198245

199246
class Plist(Dict):
200247

201-
"""The main Plist object. Basically a dict (the toplevel object
202-
of a plist is a dict) with two additional methods to read from
203-
and write to files.
248+
"""This class has been deprecated! Use the Dict with readPlist() and
249+
writePlist() functions instead.
204250
"""
205251

206252
def fromFile(cls, pathOrFile):
207-
didOpen = 0
208-
if isinstance(pathOrFile, (str, unicode)):
209-
pathOrFile = open(pathOrFile)
210-
didOpen = 1
211-
p = PlistParser()
212-
plist = p.parse(pathOrFile)
213-
if didOpen:
214-
pathOrFile.close()
253+
"""Deprecated! Use the readPlist() function instead."""
254+
rootObject = readPlist(pathOrFile)
255+
plist = cls()
256+
plist.update(rootObject)
215257
return plist
216258
fromFile = classmethod(fromFile)
217259

218260
def write(self, pathOrFile):
219-
if isinstance(pathOrFile, (str, unicode)):
220-
pathOrFile = open(pathOrFile, "w")
221-
didOpen = 1
222-
else:
223-
didOpen = 0
224-
225-
writer = PlistWriter(pathOrFile)
226-
writer.writeln("<plist version=\"1.0\">")
227-
writer.writeDict(self.__dict__)
228-
writer.writeln("</plist>")
229-
230-
if didOpen:
231-
pathOrFile.close()
261+
"""Deprecated! Use the writePlist() function instead."""
262+
writePlist(self, pathOrFile)
232263

233264

234265
class Data:
@@ -323,7 +354,7 @@ def addObject(self, value):
323354
self.currentKey = None
324355
elif not self.stack:
325356
# this is the root object
326-
assert self.root is value
357+
self.root = value
327358
else:
328359
self.stack[-1].append(value)
329360

@@ -339,10 +370,7 @@ def getData(self):
339370
# element handlers
340371

341372
def begin_dict(self, attrs):
342-
if self.root is None:
343-
self.root = d = Plist()
344-
else:
345-
d = Dict()
373+
d = Dict()
346374
self.addObject(d)
347375
self.stack.append(d)
348376
def end_dict(self):
@@ -401,7 +429,7 @@ def __repr__(self):
401429
from StringIO import StringIO
402430
import time
403431
if len(sys.argv) == 1:
404-
pl = Plist(
432+
pl = Dict(
405433
aString="Doodah",
406434
aList=["A", "B", 12, 32.1, [1, 2, 3]],
407435
aFloat = 0.1,
@@ -414,24 +442,24 @@ def __repr__(self):
414442
),
415443
someData = Data("<binary gunk>"),
416444
someMoreData = Data("<lots of binary gunk>" * 10),
417-
aDate = Date(time.mktime(time.gmtime())),
445+
# aDate = Date(time.mktime(time.gmtime())),
418446
)
419447
elif len(sys.argv) == 2:
420-
pl = Plist.fromFile(sys.argv[1])
448+
pl = readPlist(sys.argv[1])
421449
else:
422450
print "Too many arguments: at most 1 plist file can be given."
423451
sys.exit(1)
424452

425453
# unicode keys are possible, but a little awkward to use:
426454
pl[u'\xc5benraa'] = "That was a unicode key."
427455
f = StringIO()
428-
pl.write(f)
456+
writePlist(pl, f)
429457
xml = f.getvalue()
430458
print xml
431459
f.seek(0)
432-
pl2 = Plist.fromFile(f)
460+
pl2 = readPlist(f)
433461
assert pl == pl2
434462
f = StringIO()
435-
pl2.write(f)
463+
writePlist(pl2, f)
436464
assert xml == f.getvalue()
437465
#print repr(pl2)

0 commit comments

Comments
 (0)