1 """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
67 def readPlist(pathOrFile
):
68 """Read a .plist file. 'pathOrFile' may either be a file name or a
69 (readable) file object. Return the unpacked root object (which
70 usually is a dictionary).
73 if isinstance(pathOrFile
, (str, unicode)):
74 pathOrFile
= open(pathOrFile
)
77 rootObject
= p
.parse(pathOrFile
)
83 def writePlist(rootObject
, pathOrFile
):
84 """Write 'rootObject' to a .plist file. 'pathOrFile' may either be a
85 file name or a (writable) file object.
88 if isinstance(pathOrFile
, (str, unicode)):
89 pathOrFile
= open(pathOrFile
, "w")
91 writer
= PlistWriter(pathOrFile
)
92 writer
.writeln("<plist version=\"1.0\">")
93 writer
.writeValue(rootObject
)
94 writer
.writeln("</plist>")
99 def readPlistFromString(data
):
100 """Read a plist data from a string. Return the root object.
102 return readPlist(StringIO(data
))
105 def writePlistToString(rootObject
):
106 """Return 'rootObject' as a plist-formatted string.
109 writePlist(rootObject
, f
)
113 def readPlistFromResource(path
, restype
='plst', resid
=0):
114 """Read plst resource from the resource fork of path.
116 from Carbon
.File
import FSRef
, FSGetResourceForkName
117 from Carbon
.Files
import fsRdPerm
118 from Carbon
import Res
120 resNum
= Res
.FSOpenResourceFile(fsRef
, FSGetResourceForkName(), fsRdPerm
)
121 Res
.UseResFile(resNum
)
122 plistData
= Res
.Get1Resource(restype
, resid
).data
123 Res
.CloseResFile(resNum
)
124 return readPlistFromString(plistData
)
127 def writePlistToResource(rootObject
, path
, restype
='plst', resid
=0):
128 """Write 'rootObject' as a plst resource to the resource fork of path.
130 from Carbon
.File
import FSRef
, FSGetResourceForkName
131 from Carbon
.Files
import fsRdWrPerm
132 from Carbon
import Res
133 plistData
= writePlistToString(rootObject
)
135 resNum
= Res
.FSOpenResourceFile(fsRef
, FSGetResourceForkName(), fsRdWrPerm
)
136 Res
.UseResFile(resNum
)
138 Res
.Get1Resource(restype
, resid
).RemoveResource()
141 res
= Res
.Resource(plistData
)
142 res
.AddResource(restype
, resid
, '')
144 Res
.CloseResFile(resNum
)
149 def __init__(self
, file, indentLevel
=0, indent
="\t"):
152 self
.indentLevel
= indentLevel
155 def beginElement(self
, element
):
156 self
.stack
.append(element
)
157 self
.writeln("<%s>" % element
)
158 self
.indentLevel
+= 1
160 def endElement(self
, element
):
161 assert self
.indentLevel
> 0
162 assert self
.stack
.pop() == element
163 self
.indentLevel
-= 1
164 self
.writeln("</%s>" % element
)
166 def simpleElement(self
, element
, value
=None):
167 if value
is not None:
168 value
= _escapeAndEncode(value
)
169 self
.writeln("<%s>%s</%s>" % (element
, value
, element
))
171 self
.writeln("<%s/>" % element
)
173 def writeln(self
, line
):
175 self
.file.write(self
.indentLevel
* self
.indent
+ line
+ "\n")
177 self
.file.write("\n")
180 # Contents should conform to a subset of ISO 8601
181 # (in particular, YYYY '-' MM '-' DD 'T' HH ':' MM ':' SS 'Z'. Smaller units may be omitted with
182 # a loss of precision)
183 _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")
185 def _dateFromString(s
):
186 order
= ('year', 'month', 'day', 'hour', 'minute', 'second')
187 gd
= _dateParser
.match(s
).groupdict()
194 return datetime
.datetime(*lst
)
196 def _dateToString(d
):
197 return '%04d-%02d-%02dT%02d:%02d:%02dZ' % (
198 d
.year
, d
.month
, d
.day
,
199 d
.hour
, d
.minute
, d
.second
203 # Regex to find any control chars, except for \t \n and \r
204 _controlCharPat
= re
.compile(
205 r
"[\x00\x01\x02\x03\x04\x05\x06\x07\x08\x0b\x0c\x0e\x0f"
206 r
"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f]")
208 def _escapeAndEncode(text
):
209 m
= _controlCharPat
.search(text
)
211 raise ValueError("strings can't contains control characters; "
212 "use plistlib.Data instead")
213 text
= text
.replace("\r\n", "\n") # convert DOS line endings
214 text
= text
.replace("\r", "\n") # convert Mac line endings
215 text
= text
.replace("&", "&") # escape '&'
216 text
= text
.replace("<", "<") # escape '<'
217 text
= text
.replace(">", ">") # escape '>'
218 return text
.encode("utf-8") # encode as UTF-8
222 <?xml version="1.0" encoding="UTF-8"?>
223 <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
226 class PlistWriter(DumbXMLWriter
):
228 def __init__(self
, file, indentLevel
=0, indent
="\t", writeHeader
=1):
230 file.write(PLISTHEADER
)
231 DumbXMLWriter
.__init
__(self
, file, indentLevel
, indent
)
233 def writeValue(self
, value
):
234 if isinstance(value
, (str, unicode)):
235 self
.simpleElement("string", value
)
236 elif isinstance(value
, bool):
237 # must switch for bool before int, as bool is a
240 self
.simpleElement("true")
242 self
.simpleElement("false")
243 elif isinstance(value
, (int, long)):
244 self
.simpleElement("integer", "%d" % value
)
245 elif isinstance(value
, float):
246 self
.simpleElement("real", repr(value
))
247 elif isinstance(value
, dict):
248 self
.writeDict(value
)
249 elif isinstance(value
, Data
):
250 self
.writeData(value
)
251 elif isinstance(value
, datetime
.datetime
):
252 self
.simpleElement("date", _dateToString(value
))
253 elif isinstance(value
, (tuple, list)):
254 self
.writeArray(value
)
256 raise TypeError("unsuported type: %s" % type(value
))
258 def writeData(self
, data
):
259 self
.beginElement("data")
260 self
.indentLevel
-= 1
261 maxlinelength
= 76 - len(self
.indent
.replace("\t", " " * 8) *
263 for line
in data
.asBase64(maxlinelength
).split("\n"):
266 self
.indentLevel
+= 1
267 self
.endElement("data")
269 def writeDict(self
, d
):
270 self
.beginElement("dict")
273 for key
, value
in items
:
274 if not isinstance(key
, (str, unicode)):
275 raise TypeError("keys must be strings")
276 self
.simpleElement("key", key
)
277 self
.writeValue(value
)
278 self
.endElement("dict")
280 def writeArray(self
, array
):
281 self
.beginElement("array")
283 self
.writeValue(value
)
284 self
.endElement("array")
287 class _InternalDict(dict):
289 # This class is needed while Dict is scheduled for deprecation:
290 # we only need to warn when a *user* instantiates Dict or when
291 # the "attribute notation for dict keys" is used.
293 def __getattr__(self
, attr
):
297 raise AttributeError, attr
298 from warnings
import warn
299 warn("Attribute access from plist dicts is deprecated, use d[key] "
300 "notation instead", PendingDeprecationWarning
)
303 def __setattr__(self
, attr
, value
):
304 from warnings
import warn
305 warn("Attribute access from plist dicts is deprecated, use d[key] "
306 "notation instead", PendingDeprecationWarning
)
309 def __delattr__(self
, attr
):
313 raise AttributeError, attr
314 from warnings
import warn
315 warn("Attribute access from plist dicts is deprecated, use d[key] "
316 "notation instead", PendingDeprecationWarning
)
318 class Dict(_InternalDict
):
320 def __init__(self
, **kwargs
):
321 from warnings
import warn
322 warn("The plistlib.Dict class is deprecated, use builtin dict instead",
323 PendingDeprecationWarning
)
324 super(Dict
, self
).__init
__(**kwargs
)
327 class Plist(_InternalDict
):
329 """This class has been deprecated. Use readPlist() and writePlist()
330 functions instead, together with regular dict objects.
333 def __init__(self
, **kwargs
):
334 from warnings
import warn
335 warn("The Plist class is deprecated, use the readPlist() and "
336 "writePlist() functions instead", PendingDeprecationWarning
)
337 super(Plist
, self
).__init
__(**kwargs
)
339 def fromFile(cls
, pathOrFile
):
340 """Deprecated. Use the readPlist() function instead."""
341 rootObject
= readPlist(pathOrFile
)
343 plist
.update(rootObject
)
345 fromFile
= classmethod(fromFile
)
347 def write(self
, pathOrFile
):
348 """Deprecated. Use the writePlist() function instead."""
349 writePlist(self
, pathOrFile
)
352 def _encodeBase64(s
, maxlinelength
=76):
353 # copied from base64.encodestring(), with added maxlinelength argument
354 maxbinsize
= (maxlinelength
//4)*3
356 for i
in range(0, len(s
), maxbinsize
):
357 chunk
= s
[i
: i
+ maxbinsize
]
358 pieces
.append(binascii
.b2a_base64(chunk
))
359 return "".join(pieces
)
363 """Wrapper for binary data."""
365 def __init__(self
, data
):
368 def fromBase64(cls
, data
):
369 # base64.decodestring just calls binascii.a2b_base64;
370 # it seems overkill to use both base64 and binascii.
371 return cls(binascii
.a2b_base64(data
))
372 fromBase64
= classmethod(fromBase64
)
374 def asBase64(self
, maxlinelength
=76):
375 return _encodeBase64(self
.data
, maxlinelength
)
377 def __cmp__(self
, other
):
378 if isinstance(other
, self
.__class
__):
379 return cmp(self
.data
, other
.data
)
380 elif isinstance(other
, str):
381 return cmp(self
.data
, other
)
383 return cmp(id(self
), id(other
))
386 return "%s(%s)" % (self
.__class
__.__name
__, repr(self
.data
))
393 self
.currentKey
= None
396 def parse(self
, fileobj
):
397 from xml
.parsers
.expat
import ParserCreate
398 parser
= ParserCreate()
399 parser
.StartElementHandler
= self
.handleBeginElement
400 parser
.EndElementHandler
= self
.handleEndElement
401 parser
.CharacterDataHandler
= self
.handleData
402 parser
.ParseFile(fileobj
)
405 def handleBeginElement(self
, element
, attrs
):
407 handler
= getattr(self
, "begin_" + element
, None)
408 if handler
is not None:
411 def handleEndElement(self
, element
):
412 handler
= getattr(self
, "end_" + element
, None)
413 if handler
is not None:
416 def handleData(self
, data
):
417 self
.data
.append(data
)
419 def addObject(self
, value
):
420 if self
.currentKey
is not None:
421 self
.stack
[-1][self
.currentKey
] = value
422 self
.currentKey
= None
424 # this is the root object
427 self
.stack
[-1].append(value
)
430 data
= "".join(self
.data
)
432 data
= data
.encode("ascii")
440 def begin_dict(self
, attrs
):
448 self
.currentKey
= self
.getData()
450 def begin_array(self
, attrs
):
460 self
.addObject(False)
461 def end_integer(self
):
462 self
.addObject(int(self
.getData()))
464 self
.addObject(float(self
.getData()))
465 def end_string(self
):
466 self
.addObject(self
.getData())
468 self
.addObject(Data
.fromBase64(self
.getData()))
470 self
.addObject(_dateFromString(self
.getData()))