1 r
"""plistlib.py -- a tool to generate and parse MacOSX .plist files.
3 The PropertyList (.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.
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.
11 To parse a plist from a file, use the readPlist(pathOrFile) function,
12 with a file name or a (readable) file object as the only argument. It
13 returns the top level object (again, usually a dictionary).
15 To work with plist data in strings, you can use readPlistFromString()
16 and writePlistToString().
18 Values can be strings, integers, floats, booleans, tuples, lists,
19 dictionaries, Data or datetime.datetime objects. String values (including
20 dictionary keys) may be unicode strings -- they will be written out as
23 The <data> plist type is supported through the Data class. This is a
24 thin wrapper around a Python string.
26 Generate Plist example:
30 aList=["A", "B", 12, 32.1, [1, 2, 3]],
34 anotherString="<hello & hi there!>",
35 aUnicodeValue=u'M\xe4ssig, Ma\xdf',
39 someData=Data("<binary gunk>"),
40 someMoreData=Data("<lots of binary gunk>" * 10),
41 aDate=datetime.datetime.fromtimestamp(time.mktime(time.gmtime())),
43 # unicode keys are possible, but a little awkward to use:
44 pl[u'\xc5benraa'] = "That was a unicode key."
45 writePlist(pl, fileName)
49 pl = readPlist(pathOrFile)
55 "readPlist", "writePlist", "readPlistFromString", "writePlistToString",
56 "readPlistFromResource", "writePlistToResource",
57 "Plist", "Data", "Dict"
59 # Note: the Plist and Dict classes have been deprecated.
63 from cStringIO
import StringIO
68 def readPlist(pathOrFile
):
69 """Read a .plist file. 'pathOrFile' may either be a file name or a
70 (readable) file object. Return the unpacked root object (which
71 usually is a dictionary).
74 if isinstance(pathOrFile
, (str, unicode)):
75 pathOrFile
= open(pathOrFile
)
78 rootObject
= p
.parse(pathOrFile
)
84 def writePlist(rootObject
, pathOrFile
):
85 """Write 'rootObject' to a .plist file. 'pathOrFile' may either be a
86 file name or a (writable) file object.
89 if isinstance(pathOrFile
, (str, unicode)):
90 pathOrFile
= open(pathOrFile
, "w")
92 writer
= PlistWriter(pathOrFile
)
93 writer
.writeln("<plist version=\"1.0\">")
94 writer
.writeValue(rootObject
)
95 writer
.writeln("</plist>")
100 def readPlistFromString(data
):
101 """Read a plist data from a string. Return the root object.
103 return readPlist(StringIO(data
))
106 def writePlistToString(rootObject
):
107 """Return 'rootObject' as a plist-formatted string.
110 writePlist(rootObject
, f
)
114 def readPlistFromResource(path
, restype
='plst', resid
=0):
115 """Read plst resource from the resource fork of path.
117 warnings
.warnpy3k("In 3.x, readPlistFromResource is removed.",
119 from Carbon
.File
import FSRef
, FSGetResourceForkName
120 from Carbon
.Files
import fsRdPerm
121 from Carbon
import Res
123 resNum
= Res
.FSOpenResourceFile(fsRef
, FSGetResourceForkName(), fsRdPerm
)
124 Res
.UseResFile(resNum
)
125 plistData
= Res
.Get1Resource(restype
, resid
).data
126 Res
.CloseResFile(resNum
)
127 return readPlistFromString(plistData
)
130 def writePlistToResource(rootObject
, path
, restype
='plst', resid
=0):
131 """Write 'rootObject' as a plst resource to the resource fork of path.
133 warnings
.warnpy3k("In 3.x, writePlistToResource is removed.", stacklevel
=2)
134 from Carbon
.File
import FSRef
, FSGetResourceForkName
135 from Carbon
.Files
import fsRdWrPerm
136 from Carbon
import Res
137 plistData
= writePlistToString(rootObject
)
139 resNum
= Res
.FSOpenResourceFile(fsRef
, FSGetResourceForkName(), fsRdWrPerm
)
140 Res
.UseResFile(resNum
)
142 Res
.Get1Resource(restype
, resid
).RemoveResource()
145 res
= Res
.Resource(plistData
)
146 res
.AddResource(restype
, resid
, '')
148 Res
.CloseResFile(resNum
)
153 def __init__(self
, file, indentLevel
=0, indent
="\t"):
156 self
.indentLevel
= indentLevel
159 def beginElement(self
, element
):
160 self
.stack
.append(element
)
161 self
.writeln("<%s>" % element
)
162 self
.indentLevel
+= 1
164 def endElement(self
, element
):
165 assert self
.indentLevel
> 0
166 assert self
.stack
.pop() == element
167 self
.indentLevel
-= 1
168 self
.writeln("</%s>" % element
)
170 def simpleElement(self
, element
, value
=None):
171 if value
is not None:
172 value
= _escapeAndEncode(value
)
173 self
.writeln("<%s>%s</%s>" % (element
, value
, element
))
175 self
.writeln("<%s/>" % element
)
177 def writeln(self
, line
):
179 self
.file.write(self
.indentLevel
* self
.indent
+ line
+ "\n")
181 self
.file.write("\n")
184 # Contents should conform to a subset of ISO 8601
185 # (in particular, YYYY '-' MM '-' DD 'T' HH ':' MM ':' SS 'Z'. Smaller units may be omitted with
186 # a loss of precision)
187 _dateParser
= re
.compile(r
"(?P<year>\d\d\d\d)(?:-(?P<month>\d\d)(?:-(?P<day>\d\d)(?:T(?P<hour>\d\d)(?::(?P<minute>\d\d)(?::(?P<second>\d\d))?)?)?)?)?Z")
189 def _dateFromString(s
):
190 order
= ('year', 'month', 'day', 'hour', 'minute', 'second')
191 gd
= _dateParser
.match(s
).groupdict()
198 return datetime
.datetime(*lst
)
200 def _dateToString(d
):
201 return '%04d-%02d-%02dT%02d:%02d:%02dZ' % (
202 d
.year
, d
.month
, d
.day
,
203 d
.hour
, d
.minute
, d
.second
207 # Regex to find any control chars, except for \t \n and \r
208 _controlCharPat
= re
.compile(
209 r
"[\x00\x01\x02\x03\x04\x05\x06\x07\x08\x0b\x0c\x0e\x0f"
210 r
"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f]")
212 def _escapeAndEncode(text
):
213 m
= _controlCharPat
.search(text
)
215 raise ValueError("strings can't contains control characters; "
216 "use plistlib.Data instead")
217 text
= text
.replace("\r\n", "\n") # convert DOS line endings
218 text
= text
.replace("\r", "\n") # convert Mac line endings
219 text
= text
.replace("&", "&") # escape '&'
220 text
= text
.replace("<", "<") # escape '<'
221 text
= text
.replace(">", ">") # escape '>'
222 return text
.encode("utf-8") # encode as UTF-8
226 <?xml version="1.0" encoding="UTF-8"?>
227 <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
230 class PlistWriter(DumbXMLWriter
):
232 def __init__(self
, file, indentLevel
=0, indent
="\t", writeHeader
=1):
234 file.write(PLISTHEADER
)
235 DumbXMLWriter
.__init
__(self
, file, indentLevel
, indent
)
237 def writeValue(self
, value
):
238 if isinstance(value
, (str, unicode)):
239 self
.simpleElement("string", value
)
240 elif isinstance(value
, bool):
241 # must switch for bool before int, as bool is a
244 self
.simpleElement("true")
246 self
.simpleElement("false")
247 elif isinstance(value
, (int, long)):
248 self
.simpleElement("integer", "%d" % value
)
249 elif isinstance(value
, float):
250 self
.simpleElement("real", repr(value
))
251 elif isinstance(value
, dict):
252 self
.writeDict(value
)
253 elif isinstance(value
, Data
):
254 self
.writeData(value
)
255 elif isinstance(value
, datetime
.datetime
):
256 self
.simpleElement("date", _dateToString(value
))
257 elif isinstance(value
, (tuple, list)):
258 self
.writeArray(value
)
260 raise TypeError("unsuported type: %s" % type(value
))
262 def writeData(self
, data
):
263 self
.beginElement("data")
264 self
.indentLevel
-= 1
265 maxlinelength
= 76 - len(self
.indent
.replace("\t", " " * 8) *
267 for line
in data
.asBase64(maxlinelength
).split("\n"):
270 self
.indentLevel
+= 1
271 self
.endElement("data")
273 def writeDict(self
, d
):
274 self
.beginElement("dict")
277 for key
, value
in items
:
278 if not isinstance(key
, (str, unicode)):
279 raise TypeError("keys must be strings")
280 self
.simpleElement("key", key
)
281 self
.writeValue(value
)
282 self
.endElement("dict")
284 def writeArray(self
, array
):
285 self
.beginElement("array")
287 self
.writeValue(value
)
288 self
.endElement("array")
291 class _InternalDict(dict):
293 # This class is needed while Dict is scheduled for deprecation:
294 # we only need to warn when a *user* instantiates Dict or when
295 # the "attribute notation for dict keys" is used.
297 def __getattr__(self
, attr
):
301 raise AttributeError, attr
302 from warnings
import warn
303 warn("Attribute access from plist dicts is deprecated, use d[key] "
304 "notation instead", PendingDeprecationWarning
, 2)
307 def __setattr__(self
, attr
, value
):
308 from warnings
import warn
309 warn("Attribute access from plist dicts is deprecated, use d[key] "
310 "notation instead", PendingDeprecationWarning
, 2)
313 def __delattr__(self
, attr
):
317 raise AttributeError, attr
318 from warnings
import warn
319 warn("Attribute access from plist dicts is deprecated, use d[key] "
320 "notation instead", PendingDeprecationWarning
, 2)
322 class Dict(_InternalDict
):
324 def __init__(self
, **kwargs
):
325 from warnings
import warn
326 warn("The plistlib.Dict class is deprecated, use builtin dict instead",
327 PendingDeprecationWarning
, 2)
328 super(Dict
, self
).__init
__(**kwargs
)
331 class Plist(_InternalDict
):
333 """This class has been deprecated. Use readPlist() and writePlist()
334 functions instead, together with regular dict objects.
337 def __init__(self
, **kwargs
):
338 from warnings
import warn
339 warn("The Plist class is deprecated, use the readPlist() and "
340 "writePlist() functions instead", PendingDeprecationWarning
, 2)
341 super(Plist
, self
).__init
__(**kwargs
)
343 def fromFile(cls
, pathOrFile
):
344 """Deprecated. Use the readPlist() function instead."""
345 rootObject
= readPlist(pathOrFile
)
347 plist
.update(rootObject
)
349 fromFile
= classmethod(fromFile
)
351 def write(self
, pathOrFile
):
352 """Deprecated. Use the writePlist() function instead."""
353 writePlist(self
, pathOrFile
)
356 def _encodeBase64(s
, maxlinelength
=76):
357 # copied from base64.encodestring(), with added maxlinelength argument
358 maxbinsize
= (maxlinelength
//4)*3
360 for i
in range(0, len(s
), maxbinsize
):
361 chunk
= s
[i
: i
+ maxbinsize
]
362 pieces
.append(binascii
.b2a_base64(chunk
))
363 return "".join(pieces
)
367 """Wrapper for binary data."""
369 def __init__(self
, data
):
372 def fromBase64(cls
, data
):
373 # base64.decodestring just calls binascii.a2b_base64;
374 # it seems overkill to use both base64 and binascii.
375 return cls(binascii
.a2b_base64(data
))
376 fromBase64
= classmethod(fromBase64
)
378 def asBase64(self
, maxlinelength
=76):
379 return _encodeBase64(self
.data
, maxlinelength
)
381 def __cmp__(self
, other
):
382 if isinstance(other
, self
.__class
__):
383 return cmp(self
.data
, other
.data
)
384 elif isinstance(other
, str):
385 return cmp(self
.data
, other
)
387 return cmp(id(self
), id(other
))
390 return "%s(%s)" % (self
.__class
__.__name
__, repr(self
.data
))
397 self
.currentKey
= None
400 def parse(self
, fileobj
):
401 from xml
.parsers
.expat
import ParserCreate
402 parser
= ParserCreate()
403 parser
.StartElementHandler
= self
.handleBeginElement
404 parser
.EndElementHandler
= self
.handleEndElement
405 parser
.CharacterDataHandler
= self
.handleData
406 parser
.ParseFile(fileobj
)
409 def handleBeginElement(self
, element
, attrs
):
411 handler
= getattr(self
, "begin_" + element
, None)
412 if handler
is not None:
415 def handleEndElement(self
, element
):
416 handler
= getattr(self
, "end_" + element
, None)
417 if handler
is not None:
420 def handleData(self
, data
):
421 self
.data
.append(data
)
423 def addObject(self
, value
):
424 if self
.currentKey
is not None:
425 self
.stack
[-1][self
.currentKey
] = value
426 self
.currentKey
= None
428 # this is the root object
431 self
.stack
[-1].append(value
)
434 data
= "".join(self
.data
)
436 data
= data
.encode("ascii")
444 def begin_dict(self
, attrs
):
452 self
.currentKey
= self
.getData()
454 def begin_array(self
, attrs
):
464 self
.addObject(False)
465 def end_integer(self
):
466 self
.addObject(int(self
.getData()))
468 self
.addObject(float(self
.getData()))
469 def end_string(self
):
470 self
.addObject(self
.getData())
472 self
.addObject(Data
.fromBase64(self
.getData()))
474 self
.addObject(_dateFromString(self
.getData()))