# Property List encoding and decoding # by Mario Vilas (mvilas at gmail.com) #------------------------------------------------------------------------------ # Imports import types import base64 import datetime from xml.etree import ElementTree #------------------------------------------------------------------------------ # Exports __all__ = ["PList"] #------------------------------------------------------------------------------ # User interface class PList: @classmethod def parse(self, filename): return self.fromtree( ElementTree.parse(filename) ) @classmethod def fromstring(self, stringdata): return self.fromtree( ElementTree.fromstring(stringdata) ) @classmethod def fromtree(self, xml): if hasattr(xml, 'getroot'): xml = xml.getroot() children = xml.getchildren() if xml.tag != 'plist' or len(children) != 1: raise Exception, "Bad property list" return self.__build(children[0]) @classmethod def dump(self, filename, plist): xml = self.tostring(plist) fd = open(filename, 'w') fd.write(xml) return fd @classmethod def tostring(self, plist): xml = '\n' xml += '\n' xml += '\n' xml += self.__marshall(plist) xml += '\n' return xml @classmethod def totree(self, plist): xml = self.tostring(plist) tree = ElementTree.fromstring(xml) return tree fromfile = parse tofile = dump #------------------------------------------------------------------------------ # Internally used worker methods and classes @classmethod def __marshall(self, data): classname = self.__name__ typename = data.__class__.__name__ marshaller_name = '_%s__marshall_%s' % (classname, typename) if not hasattr(self, marshaller_name): raise KeyError, marshaller_name ## marshaller = self.__marshall_string else: marshaller = getattr(self, marshaller_name) return marshaller(data) @classmethod def __build(self, element): classname = self.__name__ typename = element.tag builder_name = '_%s__build_%s' % (classname, typename) if not hasattr(self, builder_name): raise KeyError, builder_name ## builder = self.__build_string else: builder = getattr(self, builder_name) return builder(element) @staticmethod def __properties(element): props = {} for key, value in element.items(): props[key] = value return props @staticmethod def __tag(name, value = '', props = {}): value = str(value) props = ''.join( [ (' %s="%s"' % p) for p in props.iteritems() ] ) if value: return '<%(name)s%(props)s>%(value)s\n' % vars() return '<%(name)s%(props)s />\n' % vars() #------------------------------------------------------------------------------ # Marshallers @classmethod def __marshall_dict(self, data): m = '' tags = data.keys() tags.sort() for key in tags: value = data[key] m += self.__tag('key', key) + self.__marshall(value) return self.__tag('dict', m) @classmethod def __marshall_list(self, data): children = ''.join([ self.__marshall(element) for element in data ]) return self.__tag('array', children) __marshall_tuple = __marshall_list @classmethod def __marshall_NoneType(self, data): return self.__tag('undef', '') @classmethod def __marshall_bool(self, data): if data: return self.__tag('true') return self.__tag('false') # XXX python only supports floats, we need doubles @classmethod def __marshall_float(self, data): return self.__tag('real', repr(data)) @classmethod def __marshall_int(self, data): return self.__tag('integer', data) __marshall_long = __marshall_int @classmethod def __marshall_str(self, data): return self.__tag('string', data) @classmethod def __marshall_buffer(self, data): data = base64.encodestring(str(data)) ## data = data.replace('\n', '') data = data.replace('\r', '') return self.__tag('data', data) @classmethod def __marshall_date(self, data): return self.__tag('date', str(d)) #------------------------------------------------------------------------------ # Builders @classmethod def __build_dict(self, element): data = {} children = element.getchildren() if (len(children) & 1) != 0: raise IndexError, "Incomplete dictionary" for index in range(0, len(children), 2): key = children[index] value = children[index + 1] if key.tag != 'key': raise ValueError, "Bad dictionary data" data[key.text] = self.__build(value) return data @classmethod def __build_array(self, element): return [ self.__build(child) for child in element.getchildren() ] @staticmethod def __build_undef(element): return None @staticmethod def __build_true(element): return True @staticmethod def __build_false(element): return False # XXX python only supports floats, we need doubles @staticmethod def __build_real(element): return float(element.text) @staticmethod def __build_integer(element): try: return int(element.text) except ValueError: return long(element.text) @staticmethod def __build_string(element): return element.text @classmethod def __build_data(self, element): return base64.decodestring(str(element.text)) @classmethod def __build_date(self, element): return datetime.fromtimestamp(str(element.text)) #------------------------------------------------------------------------------ # Test code if __name__ == '__main__': import sys if len(sys.argv): filename = sys.argv[1] else: filename = 'sample.plist' plist = PList.fromfile(filename) print plist print '-' * 79 xml = PList.tostring(plist) print xml