1""" 2Conversion.py -- Tools for converting between Python and Objective-C objects. 3 4Conversion offers API to convert between Python and Objective-C instances of 5various classes. Currently, the focus is on Python and Objective-C 6collections. 7""" 8 9__all__ = [ 10 'pythonCollectionFromPropertyList', 'propertyListFromPythonCollection', 11 'serializePropertyList', 'deserializePropertyList', 12 'toPythonDecimal', 'fromPythonDecimal', 13] 14 15from Foundation import * 16import datetime 17import time 18try: 19 import decimal 20except ImportError: 21 decimal = None 22 23PYTHON_TYPES = ( 24 basestring, bool, int, float, long, list, tuple, dict, 25 datetime.date, datetime.datetime, bool, buffer, type(None), 26) 27 28DECIMAL_LOCALE = NSDictionary.dictionaryWithObject_forKey_( 29 u'.', 'NSDecimalSeparator') 30 31def toPythonDecimal(aNSDecimalNumber): 32 """ 33 Convert a NSDecimalNumber to a Python decimal.Decimal 34 """ 35 return decimal.Decimal( 36 aNSDecimalNumber.descriptionWithLocale_(DECIMAL_LOCALE)) 37 38def fromPythonDecimal(aPythonDecimal): 39 """ 40 Convert a Python decimal.Decimal to a NSDecimalNumber 41 """ 42 return NSDecimalNumber.decimalNumberWithString_locale_( 43 unicode(aPythonDecimal), DECIMAL_LOCALE) 44 45FORMATS = dict( 46 xml=NSPropertyListXMLFormat_v1_0, 47 binary=NSPropertyListBinaryFormat_v1_0, 48 ascii=NSPropertyListOpenStepFormat, 49) 50 51def serializePropertyList(aPropertyList, format='xml'): 52 """ 53 Serialize a property list to an NSData object. Format is one of the 54 following strings: 55 56 xml (default): 57 NSPropertyListXMLFormat_v1_0, the XML representation 58 59 binary: 60 NSPropertyListBinaryFormat_v1_0, the efficient binary representation 61 62 ascii: 63 NSPropertyListOpenStepFormat, the old-style ASCII property list 64 65 It is expected that this property list is comprised of Objective-C 66 objects. In most cases Python data structures will work, but 67 decimal.Decimal and datetime.datetime objects are not transparently 68 bridged so it will fail in that case. If you expect to have these 69 objects in your property list, then use propertyListFromPythonCollection 70 before serializing it. 71 """ 72 try: 73 formatOption = FORMATS[format] 74 except KeyError: 75 raise TypeError("Invalid format: %s" % (format,)) 76 data, err = NSPropertyListSerialization.dataFromPropertyList_format_errorDescription_(aPropertyList, formatOption) 77 if err is not None: 78 # braindead API! 79 errStr = err.encode('utf-8') 80 err.release() 81 raise TypeError(errStr) 82 return data 83 84def deserializePropertyList(propertyListData): 85 """ 86 Deserialize a property list from a NSData, str, unicode or buffer 87 88 Returns an Objective-C property list. 89 """ 90 if isinstance(propertyListData, str): 91 propertyListData = buffer(propertyListData) 92 elif isinstance(propertyListData, unicode): 93 propertyListData = buffer(propertyListData.encode('utf-8')) 94 plist, fmt, err = NSPropertyListSerialization.propertyListFromData_mutabilityOption_format_errorDescription_(propertyListData, NSPropertyListMutableContainers, None, None) 95 if err is not None: 96 # braindead API! 97 errStr = err.encode('utf-8') 98 err.release() 99 raise TypeError(errStr) 100 return plist 101 102def propertyListFromPythonCollection(aPyCollection, conversionHelper=None): 103 """ 104 Convert a Python collection (dict, list, tuple, string) into an 105 Objective-C collection. 106 107 If conversionHelper is defined, it must be a callable. It will be called 108 for any object encountered for which propertyListFromPythonCollection() 109 cannot automatically convert the object. The supplied helper function 110 should convert the object and return the converted form. If the conversion 111 helper cannot convert the type, it should raise an exception or return 112 None. 113 """ 114 if isinstance(aPyCollection, dict): 115 collection = NSMutableDictionary.dictionary() 116 for aKey in aPyCollection: 117 if not isinstance(aKey, basestring): 118 raise TypeError("Property list keys must be strings") 119 convertedValue = propertyListFromPythonCollection( 120 aPyCollection[aKey], conversionHelper=conversionHelper) 121 collection[aKey] = convertedValue 122 return collection 123 elif isinstance(aPyCollection, (list, tuple)): 124 collection = NSMutableArray.array() 125 for aValue in aPyCollection: 126 convertedValue = propertyListFromPythonCollection(aValue, 127 conversionHelper=conversionHelper) 128 collection.append(aValue) 129 return collection 130 elif isinstance(aPyCollection, (datetime.datetime, datetime.date)): 131 return NSDate.dateWithTimeIntervalSince1970_( 132 time.mktime(aPyCollection.timetuple())) 133 elif decimal is not None and isinstance(aPyCollection, decimal.Decimal): 134 return fromPythonDecimal(aPyCollection) 135 elif isinstance(aPyCollection, PYTHON_TYPES): 136 # bridge will convert 137 return aPyCollection 138 elif conversionHelper is not None: 139 return conversionHelper(aPyCollection) 140 raise TypeError("Type '%s' encountered in Python collection; don't know how to convert." % type(aPyCollection)) 141 142 143def pythonCollectionFromPropertyList(aCollection, conversionHelper=None): 144 """ 145 Converts a Foundation based property list into a Python 146 collection (all members will be instances or subclasses of standard Python 147 types) 148 149 Like propertyListFromPythonCollection(), conversionHelper is an optional 150 callable that will be invoked any time an encountered object cannot be 151 converted. 152 """ 153 if isinstance(aCollection, NSDictionary): 154 pyCollection = {} 155 for k in aCollection: 156 if not isinstance(k, basestring): 157 raise TypeError("Property list keys must be strings") 158 convertedValue = pythonCollectionFromPropertyList( 159 aCollection[k], conversionHelper) 160 pyCollection[k] = convertedValue 161 return pyCollection 162 elif isinstance(aCollection, NSArray): 163 return [ 164 pythonCollectionFromPropertyList(item, conversionHelper) 165 for item in aCollection 166 ] 167 elif isinstance(aCollection, NSData): 168 return buffer(aCollection) 169 elif isinstance(aCollection, NSDate): 170 return datetime.datetime.fromtimestamp( 171 aCollection.timeIntervalSince1970()) 172 elif isinstance(aCollection, NSDecimalNumber) and decimal is not None: 173 return toPythonDecimal(aCollection) 174 elif aCollection is NSNull.null(): 175 return None 176 elif isinstance(aCollection, PYTHON_TYPES): 177 return aCollection 178 elif conversionHelper: 179 return conversionHelper(aCollection) 180 raise TypeError("Type '%s' encountered in ObjC collection; don't know how to convert." % type(aCollection)) 181