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