diff options
author | Just van Rossum <just@letterror.com> | 2002-11-19 22:01:02 (GMT) |
---|---|---|
committer | Just van Rossum <just@letterror.com> | 2002-11-19 22:01:02 (GMT) |
commit | 0ec2744fb8416c553a266230021c8124a6f38553 (patch) | |
tree | 6192c7353068e55a3d36ac038eef52e1e1655f76 /Mac/Lib | |
parent | 72f861657a64ce708efcbb7683846cea99ec37c6 (diff) | |
download | cpython-0ec2744fb8416c553a266230021c8124a6f38553.zip cpython-0ec2744fb8416c553a266230021c8124a6f38553.tar.gz cpython-0ec2744fb8416c553a266230021c8124a6f38553.tar.bz2 |
Pure Python implementation of a plist generator/parser.
Diffstat (limited to 'Mac/Lib')
-rw-r--r-- | Mac/Lib/plistlib.py | 433 |
1 files changed, 433 insertions, 0 deletions
diff --git a/Mac/Lib/plistlib.py b/Mac/Lib/plistlib.py new file mode 100644 index 0000000..9413fd1 --- /dev/null +++ b/Mac/Lib/plistlib.py @@ -0,0 +1,433 @@ +"""plistlib.py -- a tool to generate and parse MacOSX .plist files. + +The main class in this module is Plist. It takes a set of arbitrary +keyword arguments, which will be the top level elements of the plist +dictionary. After instantiation you can add more elements by assigning +new attributes to the Plist instance. + +To write out a plist file, call the write() method of the Plist +instance with a filename or a file object. + +To parse a plist from a file, use the Plist.fromFile(pathOrFile) +classmethod, with a file name or a file object as the only argument. +(Warning: you need pyexpat installed for this to work, ie. it doesn't +work with a vanilla Python 2.2 as shipped with MacOS X.2.) + +Values can be strings, integers, floats, booleans, tuples, lists, +dictionaries, Data or Date objects. String values (including dictionary +keys) may be unicode strings -- they will be written out as UTF-8. + +For convenience, this module exports a class named Dict(), which +allows you to easily construct (nested) dicts using keyword arguments. +But regular dicts work, too. + +To support Boolean values in plists with Python < 2.3, "bool", "True" +and "False" are exported. Use these symbols from this module if you +want to be compatible with Python 2.2.x (strongly recommended). + +The <data> plist type is supported through the Data class. This is a +thin wrapper around a Python string. + +The <date> plist data has (limited) support through the Date class. +(Warning: Dates are only supported if the PyXML package is installed.) + +Generate Plist example: + + pl = Plist( + Foo="Doodah", + aList=["A", "B", 12, 32.1, [1, 2, 3]], + aFloat = 0.1, + anInt = 728, + aDict=Dict( + aString="<hello & hi there!>", + SomeUnicodeValue=u'M\xe4ssig, Ma\xdf', + someTrueValue=True, + someFalseValue=False, + ), + someData = Data("hello there!"), + someMoreData = Data("hello there! " * 10), + aDate = Date(time.mktime(time.gmtime())), + ) + # unicode keys are possible, but a little awkward to use: + pl[u'\xc5benraa'] = "That was a unicode key." + pl.write(fileName) + +Parse Plist example: + + pl = Plist.fromFile(pathOrFile) + print pl.aKey + + +""" + +# written by Just van Rossum (just@letterror.com), 2002-11-19 + + +__all__ = ["Plist", "Data", "Date", "Dict", "False", "True", "bool"] + + +INDENT = "\t" + + +class DumbXMLWriter: + + def __init__(self, file): + self.file = file + self.stack = [] + self.indentLevel = 0 + + def beginElement(self, element): + self.stack.append(element) + element = _encode(element) + self.writeln("<%s>" % element) + self.indentLevel += 1 + + def endElement(self, element): + assert self.indentLevel > 0 + assert self.stack.pop() == element + self.indentLevel -= 1 + self.writeln("</%s>" % element) + + def simpleElement(self, element, value=None): + if value: + element = _encode(element) + value = _encode(value) + self.writeln("<%s>%s</%s>" % (element, value, element)) + else: + self.writeln("<%s/>" % element) + + def writeln(self, line): + if line: + self.file.write(self.indentLevel * INDENT + line + "\n") + else: + self.file.write("\n") + + +def _encode(text): + text = text.replace("&", "&") + text = text.replace("<", "<") + return text.encode("utf-8") + + +PLISTHEADER = """\ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +""" + +class PlistWriter(DumbXMLWriter): + + def __init__(self, file): + file.write(PLISTHEADER) + DumbXMLWriter.__init__(self, file) + + def writeValue(self, value): + if isinstance(value, (str, unicode)): + self.simpleElement("string", value) + elif isinstance(value, bool): + # must switch for bool before int, as bool is a + # subclass of int... + if value: + self.simpleElement("true") + else: + self.simpleElement("false") + elif isinstance(value, int): + self.simpleElement("integer", str(value)) + elif isinstance(value, float): + # should perhaps use repr() for better precision? + self.simpleElement("real", str(value)) + elif isinstance(value, (dict, Dict)): + self.writeDict(value) + elif isinstance(value, Data): + self.writeData(value) + elif isinstance(value, Date): + self.simpleElement("date", value.toString()) + elif isinstance(value, (tuple, list)): + self.writeArray(value) + else: + assert 0, "unsuported type: %s" % type(value) + + def writeData(self, data): + self.beginElement("data") + for line in data.asBase64().split("\n"): + if line: + self.writeln(line.strip()) + self.endElement("data") + + def writeDict(self, d): + self.beginElement("dict") + items = d.items() + items.sort() + for key, value in items: + assert isinstance(key, (str, unicode)), "keys must be strings" + self.simpleElement("key", key) + self.writeValue(value) + self.endElement("dict") + + def writeArray(self, array): + self.beginElement("array") + for value in array: + self.writeValue(value) + self.endElement("array") + + +class Dict: + + """Dict wrapper for convenient acces of values through attributes.""" + + def __init__(self, **args): + self.__dict__.update(args) + + def __cmp__(self, other): + if isinstance(other, self.__class__): + return cmp(self.__dict__, other.__dict__) + elif isinstance(other, dict): + return cmp(self.__dict__, other) + else: + return cmp(id(self), id(other)) + + def __str__(self): + return "%s(**%s)" % (self.__class__.__name__, self.__dict__) + __repr__ = __str__ + + def __getattr__(self, attr): + """Delegate everything else to the dict object.""" + return getattr(self.__dict__, attr) + + +class Plist(Dict): + + """The main Plist object. Basically a dict (the toplevel object of + a plist is a dict) with one additional method: write().""" + + def fromFile(cls, pathOrFile): + didOpen = 0 + if not hasattr(pathOrFile, "write"): + pathOrFile = open(pathOrFile) + didOpen = 1 + p = PlistParser() + plist = p.parse(pathOrFile) + if didOpen: + pathOrFile.close() + return plist + fromFile = classmethod(fromFile) + + def write(self, pathOrFile): + if not hasattr(pathOrFile, "write"): + pathOrFile = open(pathOrFile, "w") + didOpen = 1 + else: + didOpen = 0 + + writer = PlistWriter(pathOrFile) + writer.writeln("<plist version=\"1.0\">") + writer.writeDict(self.__dict__) + writer.writeln("</plist>") + + if didOpen: + pathOrFile.close() + + +class Data: + + """Wrapper for binary data.""" + + def __init__(self, data): + self.data = data + + def fromBase64(cls, data): + import base64 + return cls(base64.decodestring(data)) + fromBase64 = classmethod(fromBase64) + + def asBase64(self): + import base64 + return base64.encodestring(self.data) + + def __cmp__(self, other): + if isinstance(other, self.__class__): + return cmp(self.data, other.data) + elif isinstance(other, str): + return cmp(self.data, other) + else: + return cmp(id(self), id(other)) + + def __repr__(self): + return "%s(%s)" % (self.__class__.__name__, repr(self.data)) + + +class Date: + + """Primitive date wrapper, uses time floats internally, is agnostic + about time zones. + """ + + def __init__(self, date): + if isinstance(date, str): + from xml.utils.iso8601 import parse + date = parse(date) + self.date = date + + def toString(self): + from xml.utils.iso8601 import tostring + return tostring(self.date) + + def __cmp__(self, other): + if isinstance(other, self.__class__): + return cmp(self.date, other.date) + elif isinstance(other, (int, float)): + return cmp(self.date, other) + else: + return cmp(id(self), id(other)) + + def __repr__(self): + return "%s(%s)" % (self.__class__.__name__, repr(self.toString())) + + +class PlistParser: + + def __init__(self): + self.stack = [] + self.currentKey = None + self.root = None + + def parse(self, file): + from xml.parsers.expat import ParserCreate + parser = ParserCreate() + parser.StartElementHandler = self.handleBeginElement + parser.EndElementHandler = self.handleEndElement + parser.CharacterDataHandler = self.handleData + parser.ParseFile(file) + return self.root + + def handleBeginElement(self, element, attrs): + self.data = [] + handler = getattr(self, "begin_" + element, None) + if handler is not None: + handler(attrs) + + def handleEndElement(self, element): + handler = getattr(self, "end_" + element, None) + if handler is not None: + handler() + + def handleData(self, data): + self.data.append(data) + + def addObject(self, value): + if self.currentKey is not None: + self.stack[-1][self.currentKey] = value + self.currentKey = None + elif not self.stack: + # this is the root object + assert self.root is value + else: + self.stack[-1].append(value) + + def getData(self): + data = "".join(self.data) + try: + data = data.encode("ascii") + except UnicodeError: + pass + self.data = [] + return data + + # element handlers + + def begin_dict(self, attrs): + if self.root is None: + self.root = d = Plist() + else: + d = Dict() + self.addObject(d) + self.stack.append(d) + def end_dict(self): + self.stack.pop() + + def end_key(self): + self.currentKey = self.getData() + + def begin_array(self, attrs): + a = [] + self.addObject(a) + self.stack.append(a) + def end_array(self): + self.stack.pop() + + def end_true(self): + self.addObject(True) + def end_false(self): + self.addObject(False) + def end_integer(self): + self.addObject(int(self.getData())) + def end_real(self): + self.addObject(float(self.getData())) + def end_string(self): + self.addObject(self.getData()) + def end_data(self): + self.addObject(Data.fromBase64(self.getData())) + def end_date(self): + self.addObject(Date(self.getData())) + + +# cruft to support booleans in Python <= 2.3 +import sys +if sys.version_info[:2] < (2, 3): + # Python 2.2 and earlier: no booleans + # Python 2.2.x: booleans are ints + class bool(int): + """Imitation of the Python 2.3 bool object.""" + def __new__(cls, value): + return int.__new__(cls, not not value) + def __repr__(self): + if self: + return "True" + else: + return "False" + True = bool(1) + False = bool(0) +else: + import __builtin__ + True = __builtin__.True + False = __builtin__.False + bool = __builtin__.bool + + +if __name__ == "__main__": + from StringIO import StringIO + import time + if len(sys.argv) == 1: + pl = Plist( + Foo="Doodah", + aList=["A", "B", 12, 32.1, [1, 2, 3]], + aFloat = 0.1, + anInt = 728, + aDict=Dict( + aString="<hello & hi there!>", + SomeUnicodeValue=u'M\xe4ssig, Ma\xdf', + someTrueValue=True, + someFalseValue=False, + ), + someData = Data("hello there!"), + someMoreData = Data("hello there! " * 10), + aDate = Date(time.mktime(time.gmtime())), + ) + elif len(sys.argv) == 2: + pl = Plist.fromFile(sys.argv[1]) + else: + print "Too many arguments: at most 1 plist file can be given." + sys.exit(1) + + # unicode keys are possible, but a little awkward to use: + pl[u'\xc5benraa'] = "That was a unicode key." + f = StringIO() + pl.write(f) + xml = f.getvalue() + print xml + f.seek(0) + pl2 = Plist.fromFile(f) + assert pl == pl2 + f = StringIO() + pl2.write(f) + assert xml == f.getvalue() + #print repr(pl2) |