1 """plistlib.py -- a tool to generate and parse MacOSX .plist files.
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.
8 To write out a plist file, call the write() method of the Plist
9 instance with a filename or a file object.
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.
13 (Warning: you need pyexpat installed for this to work, ie. it doesn't
14 work with a vanilla Python 2.2 as shipped with MacOS X.2.)
16 Values can be strings, integers, floats, booleans, tuples, lists,
17 dictionaries, Data or Date objects. String values (including dictionary
18 keys) may be unicode strings -- they will be written out as UTF-8.
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.
24 To support Boolean values in plists with Python < 2.3, "bool", "True"
25 and "False" are exported. Use these symbols from this module if you
26 want to be compatible with Python 2.2.x (strongly recommended).
28 The <data> plist type is supported through the Data class. This is a
29 thin wrapper around a Python string.
31 The <date> plist data has (limited) support through the Date class.
32 (Warning: Dates are only supported if the PyXML package is installed.)
34 Generate Plist example:
38 aList=["A", "B", 12, 32.1, [1, 2, 3]],
42 anotherString="<hello & hi there!>",
43 aUnicodeValue=u'M\xe4ssig, Ma\xdf',
47 someData = Data("<binary gunk>"),
48 someMoreData = Data("<lots of binary gunk>" * 10),
49 aDate = Date(time.mktime(time.gmtime())),
51 # unicode keys are possible, but a little awkward to use:
52 pl[u'\xc5benraa'] = "That was a unicode key."
57 pl = Plist.fromFile(pathOrFile)
63 # written by Just van Rossum (just@letterror.com), 2002-11-19
66 __all__
= ["Plist", "Data", "Date", "Dict", "False", "True", "bool"]
74 def __init__(self
, file):
79 def beginElement(self
, element
):
80 self
.stack
.append(element
)
81 self
.writeln("<%s>" % element
)
84 def endElement(self
, element
):
85 assert self
.indentLevel
> 0
86 assert self
.stack
.pop() == element
88 self
.writeln("</%s>" % element
)
90 def simpleElement(self
, element
, value
=None):
92 value
= _encode(value
)
93 self
.writeln("<%s>%s</%s>" % (element
, value
, element
))
95 self
.writeln("<%s/>" % element
)
97 def writeln(self
, line
):
99 self
.file.write(self
.indentLevel
* INDENT
+ line
+ "\n")
101 self
.file.write("\n")
105 text
= text
.replace("&", "&")
106 text
= text
.replace("<", "<")
107 return text
.encode("utf-8")
111 <?xml version="1.0" encoding="UTF-8"?>
112 <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
115 class PlistWriter(DumbXMLWriter
):
117 def __init__(self
, file):
118 file.write(PLISTHEADER
)
119 DumbXMLWriter
.__init
__(self
, file)
121 def writeValue(self
, value
):
122 if isinstance(value
, (str, unicode)):
123 self
.simpleElement("string", value
)
124 elif isinstance(value
, bool):
125 # must switch for bool before int, as bool is a
128 self
.simpleElement("true")
130 self
.simpleElement("false")
131 elif isinstance(value
, int):
132 self
.simpleElement("integer", str(value
))
133 elif isinstance(value
, float):
134 # should perhaps use repr() for better precision?
135 self
.simpleElement("real", str(value
))
136 elif isinstance(value
, (dict, Dict
)):
137 self
.writeDict(value
)
138 elif isinstance(value
, Data
):
139 self
.writeData(value
)
140 elif isinstance(value
, Date
):
141 self
.simpleElement("date", value
.toString())
142 elif isinstance(value
, (tuple, list)):
143 self
.writeArray(value
)
145 assert 0, "unsuported type: %s" % type(value
)
147 def writeData(self
, data
):
148 self
.beginElement("data")
149 for line
in data
.asBase64().split("\n"):
152 self
.endElement("data")
154 def writeDict(self
, d
):
155 self
.beginElement("dict")
158 for key
, value
in items
:
159 assert isinstance(key
, (str, unicode)), "keys must be strings"
160 self
.simpleElement("key", key
)
161 self
.writeValue(value
)
162 self
.endElement("dict")
164 def writeArray(self
, array
):
165 self
.beginElement("array")
167 self
.writeValue(value
)
168 self
.endElement("array")
173 """Dict wrapper for convenient access of values through attributes."""
175 def __init__(self
, **kwargs
):
176 self
.__dict
__.update(kwargs
)
178 def __cmp__(self
, other
):
179 if isinstance(other
, self
.__class
__):
180 return cmp(self
.__dict
__, other
.__dict
__)
181 elif isinstance(other
, dict):
182 return cmp(self
.__dict
__, other
)
184 return cmp(id(self
), id(other
))
187 return "%s(**%s)" % (self
.__class
__.__name
__, self
.__dict
__)
191 return self
.__class
__(**self
.__dict
__)
193 def __getattr__(self
, attr
):
194 """Delegate everything else to the dict object."""
195 return getattr(self
.__dict
__, attr
)
200 """The main Plist object. Basically a dict (the toplevel object
201 of a plist is a dict) with two additional methods to read from
205 def fromFile(cls
, pathOrFile
):
207 if not hasattr(pathOrFile
, "write"):
208 pathOrFile
= open(pathOrFile
)
211 plist
= p
.parse(pathOrFile
)
215 fromFile
= classmethod(fromFile
)
217 def write(self
, pathOrFile
):
218 if not hasattr(pathOrFile
, "write"):
219 pathOrFile
= open(pathOrFile
, "w")
224 writer
= PlistWriter(pathOrFile
)
225 writer
.writeln("<plist version=\"1.0\">")
226 writer
.writeDict(self
.__dict
__)
227 writer
.writeln("</plist>")
235 """Wrapper for binary data."""
237 def __init__(self
, data
):
240 def fromBase64(cls
, data
):
242 return cls(base64
.decodestring(data
))
243 fromBase64
= classmethod(fromBase64
)
247 return base64
.encodestring(self
.data
)
249 def __cmp__(self
, other
):
250 if isinstance(other
, self
.__class
__):
251 return cmp(self
.data
, other
.data
)
252 elif isinstance(other
, str):
253 return cmp(self
.data
, other
)
255 return cmp(id(self
), id(other
))
258 return "%s(%s)" % (self
.__class
__.__name
__, repr(self
.data
))
263 """Primitive date wrapper, uses time floats internally, is agnostic
267 def __init__(self
, date
):
268 if isinstance(date
, str):
269 from xml
.utils
.iso8601
import parse
274 from xml
.utils
.iso8601
import tostring
275 return tostring(self
.date
)
277 def __cmp__(self
, other
):
278 if isinstance(other
, self
.__class
__):
279 return cmp(self
.date
, other
.date
)
280 elif isinstance(other
, (int, float)):
281 return cmp(self
.date
, other
)
283 return cmp(id(self
), id(other
))
286 return "%s(%s)" % (self
.__class
__.__name
__, repr(self
.toString()))
293 self
.currentKey
= None
296 def parse(self
, file):
297 from xml
.parsers
.expat
import ParserCreate
298 parser
= ParserCreate()
299 parser
.StartElementHandler
= self
.handleBeginElement
300 parser
.EndElementHandler
= self
.handleEndElement
301 parser
.CharacterDataHandler
= self
.handleData
302 parser
.ParseFile(file)
305 def handleBeginElement(self
, element
, attrs
):
307 handler
= getattr(self
, "begin_" + element
, None)
308 if handler
is not None:
311 def handleEndElement(self
, element
):
312 handler
= getattr(self
, "end_" + element
, None)
313 if handler
is not None:
316 def handleData(self
, data
):
317 self
.data
.append(data
)
319 def addObject(self
, value
):
320 if self
.currentKey
is not None:
321 self
.stack
[-1][self
.currentKey
] = value
322 self
.currentKey
= None
324 # this is the root object
325 assert self
.root
is value
327 self
.stack
[-1].append(value
)
330 data
= "".join(self
.data
)
332 data
= data
.encode("ascii")
340 def begin_dict(self
, attrs
):
341 if self
.root
is None:
342 self
.root
= d
= Plist()
351 self
.currentKey
= self
.getData()
353 def begin_array(self
, attrs
):
363 self
.addObject(False)
364 def end_integer(self
):
365 self
.addObject(int(self
.getData()))
367 self
.addObject(float(self
.getData()))
368 def end_string(self
):
369 self
.addObject(self
.getData())
371 self
.addObject(Data
.fromBase64(self
.getData()))
373 self
.addObject(Date(self
.getData()))
376 # cruft to support booleans in Python <= 2.3
378 if sys
.version_info
[:2] < (2, 3):
379 # Python 2.2 and earlier: no booleans
380 # Python 2.2.x: booleans are ints
382 """Imitation of the Python 2.3 bool object."""
383 def __new__(cls
, value
):
384 return int.__new
__(cls
, not not value
)
393 # Bind the boolean builtins to local names
399 if __name__
== "__main__":
400 from StringIO
import StringIO
402 if len(sys
.argv
) == 1:
405 aList
=["A", "B", 12, 32.1, [1, 2, 3]],
409 anotherString
="<hello & hi there!>",
410 aUnicodeValue
=u
'M\xe4ssig, Ma\xdf',
414 someData
= Data("<binary gunk>"),
415 someMoreData
= Data("<lots of binary gunk>" * 10),
416 aDate
= Date(time
.mktime(time
.gmtime())),
418 elif len(sys
.argv
) == 2:
419 pl
= Plist
.fromFile(sys
.argv
[1])
421 print "Too many arguments: at most 1 plist file can be given."
424 # unicode keys are possible, but a little awkward to use:
425 pl
[u
'\xc5benraa'] = "That was a unicode key."
431 pl2
= Plist
.fromFile(f
)
435 assert xml
== f
.getvalue()