mirror of
				https://github.com/python/cpython.git
				synced 2025-10-31 05:31:20 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			437 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			437 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| """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(
 | |
|         aString="Doodah",
 | |
|         aList=["A", "B", 12, 32.1, [1, 2, 3]],
 | |
|         aFloat = 0.1,
 | |
|         anInt = 728,
 | |
|         aDict=Dict(
 | |
|             anotherString="<hello & hi there!>",
 | |
|             aUnicodeValue=u'M\xe4ssig, Ma\xdf',
 | |
|             aTrueValue=True,
 | |
|             aFalseValue=False,
 | |
|         ),
 | |
|         someData = Data("<binary gunk>"),
 | |
|         someMoreData = Data("<lots of binary gunk>" * 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)
 | |
|         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 is not None:
 | |
|             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:
 | |
|             raise TypeError("unsuported type: %s" % type(value))
 | |
| 
 | |
|     def writeData(self, data):
 | |
|         self.beginElement("data")
 | |
|         for line in data.asBase64().split("\n"):
 | |
|             if line:
 | |
|                 self.writeln(line)
 | |
|         self.endElement("data")
 | |
| 
 | |
|     def writeDict(self, d):
 | |
|         self.beginElement("dict")
 | |
|         items = d.items()
 | |
|         items.sort()
 | |
|         for key, value in items:
 | |
|             if not isinstance(key, (str, unicode)):
 | |
|                 raise TypeError("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 access of values through attributes."""
 | |
| 
 | |
|     def __init__(self, **kwargs):
 | |
|         self.__dict__.update(kwargs)
 | |
| 
 | |
|     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 copy(self):
 | |
|         return self.__class__(**self.__dict__)
 | |
| 
 | |
|     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 two additional methods to read from
 | |
|     and write to files.
 | |
|     """
 | |
| 
 | |
|     def fromFile(cls, pathOrFile):
 | |
|         didOpen = 0
 | |
|         if isinstance(pathOrFile, (str, unicode)):
 | |
|             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 isinstance(pathOrFile, (str, unicode)):
 | |
|             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:
 | |
|     # Bind the boolean builtins to local names
 | |
|     True = True
 | |
|     False = False
 | |
|     bool = bool
 | |
| 
 | |
| 
 | |
| if __name__ == "__main__":
 | |
|     from StringIO import StringIO
 | |
|     import time
 | |
|     if len(sys.argv) == 1:
 | |
|         pl = Plist(
 | |
|             aString="Doodah",
 | |
|             aList=["A", "B", 12, 32.1, [1, 2, 3]],
 | |
|             aFloat = 0.1,
 | |
|             anInt = 728,
 | |
|             aDict=Dict(
 | |
|                 anotherString="<hello & hi there!>",
 | |
|                 aUnicodeValue=u'M\xe4ssig, Ma\xdf',
 | |
|                 aTrueValue=True,
 | |
|                 aFalseValue=False,
 | |
|             ),
 | |
|             someData = Data("<binary gunk>"),
 | |
|             someMoreData = Data("<lots of binary gunk>" * 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)
 | 
