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