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