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
1415work with a vanilla Python 2.2 as shipped with MacOS X.2.)
1516
1617Values can be strings, integers, floats, booleans, tuples, lists,
1718dictionaries, Data or Date objects. String values (including dictionary
1819keys) 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
2426To support Boolean values in plists with Python < 2.3, "bool", "True"
2527and "False" are exported. Use these symbols from this module if you
3335
3436Generate 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,
5052 )
5153 # unicode keys are possible, but a little awkward to use:
5254 pl[u'\xc5 benraa'] = "That was a unicode key."
53- pl.write( fileName)
55+ writePlist(pl, fileName)
5456
5557Parse 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
72104class 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 ("&" , "&" )
106- text = text .replace ("<" , "<" )
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 ("&" , "&" ) # escape '&'
146+ text = text .replace ("<" , "<" ) # escape '<'
147+ text = _controlStripper .sub ("?" , text ) # replace control chars with '?'
148+ return text .encode ("utf-8" ) # encode as UTF-8
108149
109150
110151PLISTHEADER = """\
@@ -114,9 +155,10 @@ def _encode(text):
114155
115156class 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
199246class 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
234265class 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'\xc5 benraa' ] = "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