summaryrefslogtreecommitdiff
path: root/vobject/vcard.py
diff options
context:
space:
mode:
Diffstat (limited to 'vobject/vcard.py')
-rw-r--r--vobject/vcard.py289
1 files changed, 289 insertions, 0 deletions
diff --git a/vobject/vcard.py b/vobject/vcard.py
new file mode 100644
index 0000000..01d1d42
--- /dev/null
+++ b/vobject/vcard.py
@@ -0,0 +1,289 @@
+"""Definitions and behavior for vCard 3.0"""
+
+import behavior
+import itertools
+
+from base import VObjectError, NativeError, ValidateError, ParseError, \
+ VBase, Component, ContentLine, logger, defaultSerialize, \
+ registerBehavior, backslashEscape, ascii
+from icalendar import stringToTextValues
+
+#------------------------ vCard structs ----------------------------------------
+
+class Name(object):
+ def __init__(self, family = '', given = '', additional = '', prefix = '',
+ suffix = ''):
+ """Each name attribute can be a string or a list of strings."""
+ self.family = family
+ self.given = given
+ self.additional = additional
+ self.prefix = prefix
+ self.suffix = suffix
+
+ @staticmethod
+ def toString(val):
+ """Turn a string or array value into a string."""
+ if type(val) in (list, tuple):
+ return ' '.join(val)
+ return val
+
+ def __str__(self):
+ eng_order = ('prefix', 'given', 'additional', 'family', 'suffix')
+ out = ' '.join(self.toString(getattr(self, val)) for val in eng_order)
+ return ascii(out)
+
+ def __repr__(self):
+ return "<Name: %s>" % self.__str__()
+
+ def __eq__(self, other):
+ try:
+ return (self.family == other.family and
+ self.given == other.given and
+ self.additional == other.additional and
+ self.prefix == other.prefix and
+ self.suffix == other.suffix)
+ except:
+ return False
+
+class Address(object):
+ def __init__(self, street = '', city = '', region = '', code = '',
+ country = '', box = '', extended = ''):
+ """Each name attribute can be a string or a list of strings."""
+ self.box = box
+ self.extended = extended
+ self.street = street
+ self.city = city
+ self.region = region
+ self.code = code
+ self.country = country
+
+ @staticmethod
+ def toString(val, join_char='\n'):
+ """Turn a string or array value into a string."""
+ if type(val) in (list, tuple):
+ return join_char.join(val)
+ return val
+
+ lines = ('box', 'extended', 'street')
+ one_line = ('city', 'region', 'code')
+
+ def __str__(self):
+ lines = '\n'.join(self.toString(getattr(self, val)) for val in self.lines if getattr(self, val))
+ one_line = tuple(self.toString(getattr(self, val), ' ') for val in self.one_line)
+ lines += "\n%s, %s %s" % one_line
+ if self.country:
+ lines += '\n' + self.toString(self.country)
+ return ascii(lines)
+
+ def __repr__(self):
+ return "<Address: %s>" % repr(str(self))[1:-1]
+
+ def __eq__(self, other):
+ try:
+ return (self.box == other.box and
+ self.extended == other.extended and
+ self.street == other.street and
+ self.city == other.city and
+ self.region == other.region and
+ self.code == other.code and
+ self.country == other.country)
+ except:
+ False
+
+
+#------------------------ Registered Behavior subclasses -----------------------
+
+class VCardTextBehavior(behavior.Behavior):
+ """Provide backslash escape encoding/decoding for single valued properties.
+
+ TextBehavior also deals with base64 encoding if the ENCODING parameter is
+ explicitly set to BASE64.
+
+ """
+ allowGroup = True
+ base64string = 'B'
+
+ @classmethod
+ def decode(cls, line):
+ """Remove backslash escaping from line.valueDecode line, either to remove
+ backslash espacing, or to decode base64 encoding. The content line should
+ contain a ENCODING=b for base64 encoding, but Apple Addressbook seems to
+ export a singleton parameter of 'BASE64', which does not match the 3.0
+ vCard spec. If we encouter that, then we transform the parameter to
+ ENCODING=b"""
+ if line.encoded:
+ if 'BASE64' in line.singletonparams:
+ line.singletonparams.remove('BASE64')
+ line.encoding_param = cls.base64string
+ encoding = getattr(line, 'encoding_param', None)
+ if encoding:
+ line.value = line.value.decode('base64')
+ else:
+ line.value = stringToTextValues(line.value)[0]
+ line.encoded=False
+
+ @classmethod
+ def encode(cls, line):
+ """Backslash escape line.value."""
+ if not line.encoded:
+ encoding = getattr(line, 'encoding_param', None)
+ if encoding and encoding.upper() == cls.base64string:
+ line.value = line.value.encode('base64').replace('\n', '')
+ else:
+ line.value = backslashEscape(line.value)
+ line.encoded=True
+
+
+class VCardBehavior(behavior.Behavior):
+ allowGroup = True
+ defaultBehavior = VCardTextBehavior
+
+class VCard3_0(VCardBehavior):
+ """vCard 3.0 behavior."""
+ name = 'VCARD'
+ description = 'vCard 3.0, defined in rfc2426'
+ versionString = '3.0'
+ isComponent = True
+ sortFirst = ('version', 'prodid', 'uid')
+ knownChildren = {'N': (1, 1, None),#min, max, behaviorRegistry id
+ 'FN': (1, 1, None),
+ 'VERSION': (1, 1, None),#required, auto-generated
+ 'PRODID': (0, 1, None),
+ 'LABEL': (0, None, None),
+ 'UID': (0, None, None),
+ 'ADR': (0, None, None),
+ 'ORG': (0, None, None),
+ 'PHOTO': (0, None, None),
+ 'CATEGORIES':(0, None, None)
+ }
+
+ @classmethod
+ def generateImplicitParameters(cls, obj):
+ """Create PRODID, VERSION, and VTIMEZONEs if needed.
+
+ VTIMEZONEs will need to exist whenever TZID parameters exist or when
+ datetimes with tzinfo exist.
+
+ """
+ if not hasattr(obj, 'version'):
+ obj.add(ContentLine('VERSION', [], cls.versionString))
+registerBehavior(VCard3_0, default=True)
+
+class FN(VCardTextBehavior):
+ name = "FN"
+ description = 'Formatted name'
+registerBehavior(FN)
+
+class Label(VCardTextBehavior):
+ name = "Label"
+ description = 'Formatted address'
+registerBehavior(FN)
+
+class Photo(VCardTextBehavior):
+ name = "Photo"
+ description = 'Photograph'
+ @classmethod
+ def valueRepr( cls, line ):
+ return " (BINARY PHOTO DATA at 0x%s) " % id( line.value )
+
+registerBehavior(Photo)
+
+def toListOrString(string):
+ stringList = stringToTextValues(string)
+ if len(stringList) == 1:
+ return stringList[0]
+ else:
+ return stringList
+
+def splitFields(string):
+ """Return a list of strings or lists from a Name or Address."""
+ return [toListOrString(i) for i in
+ stringToTextValues(string, listSeparator=';', charList=';')]
+
+def toList(stringOrList):
+ if isinstance(stringOrList, basestring):
+ return [stringOrList]
+ return stringOrList
+
+def serializeFields(obj, order=None):
+ """Turn an object's fields into a ';' and ',' seperated string.
+
+ If order is None, obj should be a list, backslash escape each field and
+ return a ';' separated string.
+ """
+ fields = []
+ if order is None:
+ fields = [backslashEscape(val) for val in obj]
+ else:
+ for field in order:
+ escapedValueList = [backslashEscape(val) for val in
+ toList(getattr(obj, field))]
+ fields.append(','.join(escapedValueList))
+ return ';'.join(fields)
+
+NAME_ORDER = ('family', 'given', 'additional', 'prefix', 'suffix')
+
+class NameBehavior(VCardBehavior):
+ """A structured name."""
+ hasNative = True
+
+ @staticmethod
+ def transformToNative(obj):
+ """Turn obj.value into a Name."""
+ if obj.isNative: return obj
+ obj.isNative = True
+ obj.value = Name(**dict(zip(NAME_ORDER, splitFields(obj.value))))
+ return obj
+
+ @staticmethod
+ def transformFromNative(obj):
+ """Replace the Name in obj.value with a string."""
+ obj.isNative = False
+ obj.value = serializeFields(obj.value, NAME_ORDER)
+ return obj
+registerBehavior(NameBehavior, 'N')
+
+ADDRESS_ORDER = ('box', 'extended', 'street', 'city', 'region', 'code',
+ 'country')
+
+class AddressBehavior(VCardBehavior):
+ """A structured address."""
+ hasNative = True
+
+ @staticmethod
+ def transformToNative(obj):
+ """Turn obj.value into an Address."""
+ if obj.isNative: return obj
+ obj.isNative = True
+ obj.value = Address(**dict(zip(ADDRESS_ORDER, splitFields(obj.value))))
+ return obj
+
+ @staticmethod
+ def transformFromNative(obj):
+ """Replace the Address in obj.value with a string."""
+ obj.isNative = False
+ obj.value = serializeFields(obj.value, ADDRESS_ORDER)
+ return obj
+registerBehavior(AddressBehavior, 'ADR')
+
+class OrgBehavior(VCardBehavior):
+ """A list of organization values and sub-organization values."""
+ hasNative = True
+
+ @staticmethod
+ def transformToNative(obj):
+ """Turn obj.value into a list."""
+ if obj.isNative: return obj
+ obj.isNative = True
+ obj.value = splitFields(obj.value)
+ return obj
+
+ @staticmethod
+ def transformFromNative(obj):
+ """Replace the list in obj.value with a string."""
+ if not obj.isNative: return obj
+ obj.isNative = False
+ obj.value = serializeFields(obj.value)
+ return obj
+registerBehavior(OrgBehavior, 'ORG')
+