예제 #1
0
    def add_participant(self, *obj):
        person = Person()
        ref = EventRef()
        ref.ref = self.handle
        person.add_event_ref(ref)

        try:
            EditPerson(self.dbstate, self.uistate, [], person)
        except WindowActiveError:
            pass
예제 #2
0
class VCardParser:
    """Class to read data in VCard format from a file."""
    DATE_RE = re.compile(r'^(\d{4}-\d{1,2}-\d{1,2})|(?:(\d{4})-?(\d\d)-?(\d\d))')
    GROUP_RE = re.compile(r'^(?:[-0-9A-Za-z]+\.)?(.+)$')  # see RFC 2425 sec5.8.2
    ESCAPE_CHAR = '\\'
    TOBE_ESCAPED = ['\\', ',', ';']  # order is important
    LINE_CONTINUATION = [' ', '\t']

    @staticmethod
    def name_value_split(data):
        """Property group.name:value split is on first unquoted colon."""
        colon_idx = data.find(':')
        if colon_idx < 1:
            return ()
        quote_count = data.count('"', 0, colon_idx)
        while quote_count % 2 == 1:
            colon_idx = data.find(':', colon_idx + 1)
            quote_count = data.count('"', 0, colon_idx)
        group_name, value = data[:colon_idx], data[colon_idx + 1:]
        name_parts = VCardParser.GROUP_RE.match(group_name)
        return (name_parts.group(1), value)

    @staticmethod
    def unesc(data):
        """Remove VCard escape sequences."""
        if type(data) == type('string'):
            for char in reversed(VCardParser.TOBE_ESCAPED):
                data = data.replace(VCardParser.ESCAPE_CHAR + char, char)
            return data
        elif type(data) == type([]):
            return list(map(VCardParser.unesc, data))
        else:
            raise TypeError("VCard unescaping is not implemented for "
                              "data type %s." % str(type(data)))

    @staticmethod
    def count_escapes(strng):
        """Count the number of escape characters at the end of a string."""
        count = 0
        for char in reversed(strng):
            if char != VCardParser.ESCAPE_CHAR:
                return count
            count += 1
        return count

    @staticmethod
    def split_unescaped(strng, sep):
        """Split on sep if sep is unescaped."""
        strng_parts = strng.split(sep)
        for i in reversed(range(len(strng_parts[:]))):
            if VCardParser.count_escapes(strng_parts[i]) % 2 == 1:
                # the sep was escaped so undo split
                appendix = strng_parts.pop(i + 1)
                strng_parts[i] += sep + appendix
        return strng_parts

    def __init__(self, dbase):
        self.database = dbase
        self.formatted_name = ''
        self.name_parts = ''
        self.next_line = None
        self.trans = None
        self.version = None
        self.person = None
        self.errors = []
        self.number_of_errors = 0

    def __get_next_line(self, filehandle):
        """
        Read and return the line with the next property of the VCard.

        Also if it spans multiple lines (RFC 2425 sec.5.8.1).
        """
        line = self.next_line
        self.next_line = filehandle.readline()
        self.line_num = self.line_num + 1
        while self.next_line and self.next_line[0] in self.LINE_CONTINUATION:
            line = line.rstrip("\n")
            # TODO perhaps next lines superflous because of rU open parameter?
            if len(line) > 0 and line[-1] == "\r":
                line = line[:-1]
            line += self.next_line[1:]
            self.next_line = filehandle.readline()
            self.line_num = self.line_num + 1
        if line:
            line = line.strip()
        else:
            line = None
        return line

    def __add_msg(self, problem, line=None):
        if problem != "":
            self.number_of_errors += 1
        if line:
            message = _("Line %(line)5d: %(prob)s\n") % {"line": line,
                                                         "prob": problem}
        else:
            message = problem + "\n"
        self.errors.append(message)

    def parse(self, filehandle, user):
        """
        Prepare the database and parse the input file.

        :param filehandle: open file handle positioned at start of the file
        """
        tym = time.time()
        self.person = None
        self.database.disable_signals()
        with DbTxn(_("vCard import"), self.database, batch=True) as self.trans:
            self._parse_vCard_file(filehandle)
        self.database.enable_signals()
        self.database.request_rebuild()
        tym = time.time() - tym
        # translators: leave all/any {...} untranslated
        msg = ngettext('Import Complete: {number_of} second',
                       'Import Complete: {number_of} seconds', tym
                      ).format(number_of=tym)
        LOG.debug(msg)
        if self.number_of_errors == 0:
            message = _("VCARD import report: No errors detected")
        else:
            message = _("VCARD import report: %s errors detected\n") % \
                self.number_of_errors
        if hasattr(user.uistate, 'window'):
            parent_window = user.uistate.window
        else:
            parent_window = None
        user.info(message, "".join(self.errors),
                  parent=parent_window, monospaced=True)

    def _parse_vCard_file(self, filehandle):
        """Read each line of the input file and act accordingly."""
        self.next_line = filehandle.readline()
        self.line_num = 1

        while True:
            line = self.__get_next_line(filehandle)
            if line is None:
                break
            if line == "":
                continue

            if line.find(":") == -1:
                continue
            line_parts = self.name_value_split(line)
            if not line_parts:
                continue

            # No check for escaped ; because only fields[0] is used.
            fields = line_parts[0].split(";")

            property_name = fields[0].upper()
            if property_name == "BEGIN":
                self.next_person()
            elif property_name == "END":
                self.finish_person()
            elif property_name == "VERSION":
                self.check_version(fields, line_parts[1])
            elif property_name == "FN":
                self.add_formatted_name(fields, line_parts[1])
            elif property_name == "N":
                self.add_name_parts(fields, line_parts[1])
            elif property_name == "NICKNAME":
                self.add_nicknames(fields, line_parts[1])
            elif property_name == "SORT-STRING":
                self.add_sortas(fields, line_parts[1])
            elif property_name == "ADR":
                self.add_address(fields, line_parts[1])
            elif property_name == "TEL":
                self.add_phone(fields, line_parts[1])
            elif property_name == "BDAY":
                self.add_birthday(fields, line_parts[1])
            elif property_name == "ROLE":
                self.add_occupation(fields, line_parts[1])
            elif property_name == "URL":
                self.add_url(fields, line_parts[1])
            elif property_name == "EMAIL":
                self.add_email(fields, line_parts[1])
            elif property_name == "X-GENDER" or property_name == "GENDER":
                # VCard 3.0 only has X-GENDER, GENDER is 4.0 syntax,
                # but we want to be robust here.
                self.add_gender(fields, line_parts[1])
            elif property_name == "PRODID":
                # Included cause VCards made by Gramps have this prop.
                pass
            else:
                self.__add_msg(_("Token >%(token)s< unknown. line skipped: %(line)s") %
                               {"token": (fields[0], line), "line": self.line_num - 1})

    def finish_person(self):
        """All info has been collected, write to database."""
        if self.person is not None:
            if self.add_name():
                self.database.add_person(self.person, self.trans)
        self.person = None

    def next_person(self):
        """A VCard for another person is started."""
        if self.person is not None:
            self.finish_person()
            self.__add_msg(_("BEGIN property not properly closed by END "
                           "property, Gramps can't cope with nested VCards."),
                           self.line_num - 1)
        self.person = Person()
        self.formatted_name = ''
        self.name_parts = ''

    def check_version(self, fields, data):
        """Check the version of the VCard, only version 3.0 is supported."""
        self.version = data
        if self.version != "3.0":
            raise GrampsImportError(_("Import of VCards version %s is "
                    "not supported by Gramps.") % self.version)

    def add_formatted_name(self, fields, data):
        """Read the FN property of a VCard."""
        if not self.formatted_name:
            self.formatted_name = self.unesc(str(data)).strip()

    def add_name_parts(self, fields, data):
        """Read the N property of a VCard."""
        if not self.name_parts:
            self.name_parts = data.strip()

    def add_name(self):
        """
        Add the name to the person.

        Returns True on success, False on failure.
        """
        if not self.name_parts.strip():
            self.__add_msg(_("VCard is malformed missing the compulsory N "
                           "property, so there is no name; skip it."),
                           self.line_num - 1)
            return False
        if not self.formatted_name:
            self.__add_msg(_("VCard is malformed missing the compulsory FN "
                           "property, get name from N alone."), self.line_num - 1)
        data_fields = self.split_unescaped(self.name_parts, ';')
        if len(data_fields) != 5:
            self.__add_msg(_("VCard is malformed wrong number of name "
                           "components."), self.line_num - 1)

        name = Name()
        name.set_type(NameType(NameType.BIRTH))

        if data_fields[0].strip():
            # assume first surname is primary
            for surname_str in self.split_unescaped(data_fields[0], ','):
                surname = Surname()
                prefix, sname = splitof_nameprefix(self.unesc(surname_str))
                surname.set_surname(sname.strip())
                surname.set_prefix(prefix.strip())
                name.add_surname(surname)
            name.set_primary_surname()

        if len(data_fields) > 1 and data_fields[1].strip():
            given_name = ' '.join(self.unesc(
                                  self.split_unescaped(data_fields[1], ',')))
        else:
            given_name = ''
        if len(data_fields) > 2 and data_fields[2].strip():
            additional_names = ' '.join(self.unesc(
                                     self.split_unescaped(data_fields[2], ',')))
        else:
            additional_names = ''
        self.add_firstname(given_name.strip(), additional_names.strip(), name)

        if len(data_fields) > 3 and data_fields[3].strip():
            name.set_title(' '.join(self.unesc(
                            self.split_unescaped(data_fields[3], ','))))
        if len(data_fields) > 4 and data_fields[4].strip():
            name.set_suffix(' '.join(self.unesc(
                             self.split_unescaped(data_fields[4], ','))))

        self.person.set_primary_name(name)
        return True

    def add_firstname(self, given_name, additional_names, name):
        """
        Combine given_name and additional_names and add as firstname to name.

        If possible try to add given_name as call name.
        """
        default = "%s %s" % (given_name, additional_names)
        if self.formatted_name:
            if given_name:
                if additional_names:
                    given_name_pos = self.formatted_name.find(given_name)
                    if given_name_pos != -1:
                        add_names_pos = self.formatted_name.find(additional_names)
                        if add_names_pos != -1:
                            if given_name_pos <= add_names_pos:
                                firstname = default
                                # Uncertain if given name is used as callname
                            else:
                                firstname = "%s %s" % (additional_names,
                                                       given_name)
                                name.set_call_name(given_name)
                        else:
                            idx = fitin(self.formatted_name, additional_names,
                                        given_name)
                            if idx == -1:
                                # Additional names is not in formatted name
                                firstname = default
                            else:  # Given name in middle of additional names
                                firstname = "%s%s %s" % (additional_names[:idx],
                                             given_name, additional_names[idx:])
                                name.set_call_name(given_name)
                    else:  # Given name is not in formatted name
                        firstname = default
                else:  # There are no additional_names
                    firstname = given_name
            else:  # There is no given_name
                firstname = additional_names
        else:  # There is no formatted name
            firstname = default
        name.set_first_name(firstname.strip())
        return

    def add_nicknames(self, fields, data):
        """Read the NICKNAME property of a VCard."""
        for nick in self.split_unescaped(data, ','):
            nickname = nick.strip()
            if nickname:
                name = Name()
                name.set_nick_name(self.unesc(nickname))
                self.person.add_alternate_name(name)

    def add_sortas(self, fields, data):
        """Read the SORT-STRING property of a VCard."""
        # TODO
        pass

    def add_address(self, fields, data):
        """Read the ADR property of a VCard."""
        data_fields = self.split_unescaped(data, ';')
        data_fields = [x.strip() for x in self.unesc(data_fields)]
        if ''.join(data_fields):
            addr = Address()
            def add_street(strng):
                if strng:
                    already = addr.get_street()
                    if already:
                        addr.set_street("%s %s" % (already, strng))
                    else:
                        addr.set_street(strng)
            addr.add_street = add_street
            set_func = ['add_street', 'add_street', 'add_street', 'set_city',
                        'set_state', 'set_postal_code', 'set_country']
            for i, data in enumerate(data_fields):
                if i >= len(set_func):
                    break
                getattr(addr, set_func[i])(data)
            self.person.add_address(addr)

    def add_phone(self, fields, data):
        """Read the TEL property of a VCard."""
        tel = data.strip()
        if tel:
            addr = Address()
            addr.set_phone(self.unesc(tel))
            self.person.add_address(addr)

    def add_birthday(self, fields, data):
        """Read the BDAY property of a VCard."""
        date_str = data.strip()
        date_match = VCardParser.DATE_RE.match(date_str)
        date = Date()
        if date_match:
            if date_match.group(2):
                date_str = "%s-%s-%s" % (date_match.group(2),
                                       date_match.group(3), date_match.group(4))
            else:
                date_str = date_match.group(1)
            y, m, d = [int(x, 10) for x in date_str.split('-')]
            try:
                date.set(value=(d, m, y, False))
            except DateError:
                # TRANSLATORS: leave the {vcard_snippet} untranslated
                # in the format string, but you may re-order it if needed.
                self.__add_msg(_(
                    "Invalid date in BDAY {vcard_snippet}, "
                    "preserving date as text."
                    ).format(vcard_snippet=data), self.line_num - 1)
                date.set(modifier=Date.MOD_TEXTONLY, text=data)
        else:
            if date_str:
                # TRANSLATORS: leave the {vcard_snippet} untranslated.
                self.__add_msg(_(
                    "Date {vcard_snippet} not in appropriate format "
                    "yyyy-mm-dd, preserving date as text."
                    ).format(vcard_snippet=date_str), self.line_num - 1)
                date.set(modifier=Date.MOD_TEXTONLY, text=date_str)
            else:  # silently ignore an empty BDAY record
                return
        event = Event()
        event.set_type(EventType(EventType.BIRTH))
        event.set_date_object(date)
        self.database.add_event(event, self.trans)

        event_ref = EventRef()
        event_ref.set_reference_handle(event.get_handle())
        self.person.set_birth_ref(event_ref)

    def add_occupation(self, fields, data):
        """Read the ROLE property of a VCard."""
        occupation = data.strip()
        if occupation:
            event = Event()
            event.set_type(EventType(EventType.OCCUPATION))
            event.set_description(self.unesc(occupation))
            self.database.add_event(event, self.trans)

            event_ref = EventRef()
            event_ref.set_reference_handle(event.get_handle())
            self.person.add_event_ref(event_ref)

    def add_url(self, fields, data):
        """Read the URL property of a VCard."""
        href = data.strip()
        if href:
            url = Url()
            url.set_path(self.unesc(href))
            self.person.add_url(url)

    def add_email(self, fields, data):
        """Read the EMAIL property of a VCard."""
        email = data.strip()
        if email:
            url = Url()
            url.set_type(UrlType(UrlType.EMAIL))
            url.set_path(self.unesc(email))
            self.person.add_url(url)

    def add_gender(self, fields, data):
        """Read the GENDER property of a VCard."""
        gender_value = data.strip()
        if gender_value:
            gender_value = gender_value.upper()
            gender_value = gender_value[0]
            if gender_value == 'M':
                gender = Person.MALE
            elif gender_value == 'F':
                gender = Person.FEMALE
            else:
                return
            self.person.set_gender(gender)
예제 #3
0
class VCardParser:
    """Class to read data in VCard format from a file."""
    DATE_RE = re.compile(
        r'^(\d{4}-\d{1,2}-\d{1,2})|(?:(\d{4})-?(\d\d)-?(\d\d))')
    GROUP_RE = re.compile(
        r'^(?:[-0-9A-Za-z]+\.)?(.+)$')  # see RFC 2425 sec5.8.2
    ESCAPE_CHAR = '\\'
    TOBE_ESCAPED = ['\\', ',', ';']  # order is important
    LINE_CONTINUATION = [' ', '\t']

    @staticmethod
    def name_value_split(data):
        """Property group.name:value split is on first unquoted colon."""
        colon_idx = data.find(':')
        if colon_idx < 1:
            return ()
        quote_count = data.count('"', 0, colon_idx)
        while quote_count % 2 == 1:
            colon_idx = data.find(':', colon_idx + 1)
            quote_count = data.count('"', 0, colon_idx)
        group_name, value = data[:colon_idx], data[colon_idx + 1:]
        name_parts = VCardParser.GROUP_RE.match(group_name)
        return (name_parts.group(1), value)

    @staticmethod
    def unesc(data):
        """Remove VCard escape sequences."""
        if type(data) == type('string'):
            for char in reversed(VCardParser.TOBE_ESCAPED):
                data = data.replace(VCardParser.ESCAPE_CHAR + char, char)
            return data
        elif type(data) == type([]):
            return list(map(VCardParser.unesc, data))
        else:
            raise TypeError("VCard unescaping is not implemented for "
                            "data type %s." % str(type(data)))

    @staticmethod
    def count_escapes(strng):
        """Count the number of escape characters at the end of a string."""
        count = 0
        for char in reversed(strng):
            if char != VCardParser.ESCAPE_CHAR:
                return count
            count += 1
        return count

    @staticmethod
    def split_unescaped(strng, sep):
        """Split on sep if sep is unescaped."""
        strng_parts = strng.split(sep)
        for i in reversed(range(len(strng_parts[:]))):
            if VCardParser.count_escapes(strng_parts[i]) % 2 == 1:
                # the sep was escaped so undo split
                appendix = strng_parts.pop(i + 1)
                strng_parts[i] += sep + appendix
        return strng_parts

    def __init__(self, dbase):
        self.database = dbase
        self.formatted_name = ''
        self.name_parts = ''
        self.next_line = None
        self.trans = None
        self.version = None
        self.person = None
        self.errors = []
        self.number_of_errors = 0

    def __get_next_line(self, filehandle):
        """
        Read and return the line with the next property of the VCard.

        Also if it spans multiple lines (RFC 2425 sec.5.8.1).
        """
        line = self.next_line
        self.next_line = filehandle.readline()
        self.line_num = self.line_num + 1
        while self.next_line and self.next_line[0] in self.LINE_CONTINUATION:
            line = line.rstrip("\n")
            # TODO perhaps next lines superflous because of rU open parameter?
            if len(line) > 0 and line[-1] == "\r":
                line = line[:-1]
            line += self.next_line[1:]
            self.next_line = filehandle.readline()
            self.line_num = self.line_num + 1
        if line:
            line = line.strip()
        else:
            line = None
        return line

    def __add_msg(self, problem, line=None):
        if problem != "":
            self.number_of_errors += 1
        if line:
            message = _("Line %(line)5d: %(prob)s\n") % {
                "line": line,
                "prob": problem
            }
        else:
            message = problem + "\n"
        self.errors.append(message)

    def parse(self, filehandle, user):
        """
        Prepare the database and parse the input file.

        :param filehandle: open file handle positioned at start of the file
        """
        tym = time.time()
        self.person = None
        self.database.disable_signals()
        with DbTxn(_("vCard import"), self.database, batch=True) as self.trans:
            self._parse_vCard_file(filehandle)
        self.database.enable_signals()
        self.database.request_rebuild()
        tym = time.time() - tym
        # translators: leave all/any {...} untranslated
        msg = ngettext('Import Complete: {number_of} second',
                       'Import Complete: {number_of} seconds',
                       tym).format(number_of=tym)
        LOG.debug(msg)
        if self.number_of_errors == 0:
            message = _("VCARD import report: No errors detected")
        else:
            message = _("VCARD import report: %s errors detected\n") % \
                self.number_of_errors
        if hasattr(user.uistate, 'window'):
            parent_window = user.uistate.window
        else:
            parent_window = None
        user.info(message,
                  "".join(self.errors),
                  parent=parent_window,
                  monospaced=True)

    def _parse_vCard_file(self, filehandle):
        """Read each line of the input file and act accordingly."""
        self.next_line = filehandle.readline()
        self.line_num = 1

        while True:
            line = self.__get_next_line(filehandle)
            if line is None:
                break
            if line == "":
                continue

            if line.find(":") == -1:
                continue
            line_parts = self.name_value_split(line)
            if not line_parts:
                continue

            # No check for escaped ; because only fields[0] is used.
            fields = line_parts[0].split(";")

            property_name = fields[0].upper()
            if property_name == "BEGIN":
                self.next_person()
            elif property_name == "END":
                self.finish_person()
            elif property_name == "VERSION":
                self.check_version(fields, line_parts[1])
            elif property_name == "FN":
                self.add_formatted_name(fields, line_parts[1])
            elif property_name == "N":
                self.add_name_parts(fields, line_parts[1])
            elif property_name == "NICKNAME":
                self.add_nicknames(fields, line_parts[1])
            elif property_name == "SORT-STRING":
                self.add_sortas(fields, line_parts[1])
            elif property_name == "ADR":
                self.add_address(fields, line_parts[1])
            elif property_name == "TEL":
                self.add_phone(fields, line_parts[1])
            elif property_name == "BDAY":
                self.add_birthday(fields, line_parts[1])
            elif property_name == "ROLE":
                self.add_occupation(fields, line_parts[1])
            elif property_name == "URL":
                self.add_url(fields, line_parts[1])
            elif property_name == "EMAIL":
                self.add_email(fields, line_parts[1])
            elif property_name == "X-GENDER" or property_name == "GENDER":
                # VCard 3.0 only has X-GENDER, GENDER is 4.0 syntax,
                # but we want to be robust here.
                self.add_gender(fields, line_parts[1])
            elif property_name == "PRODID":
                # Included cause VCards made by Gramps have this prop.
                pass
            else:
                self.__add_msg(
                    _("Token >%(token)s< unknown. line skipped: %(line)s") % {
                        "token": (fields[0], line),
                        "line": self.line_num - 1
                    })

    def finish_person(self):
        """All info has been collected, write to database."""
        if self.person is not None:
            if self.add_name():
                self.database.add_person(self.person, self.trans)
        self.person = None

    def next_person(self):
        """A VCard for another person is started."""
        if self.person is not None:
            self.finish_person()
            self.__add_msg(
                _("BEGIN property not properly closed by END "
                  "property, Gramps can't cope with nested VCards."),
                self.line_num - 1)
        self.person = Person()
        self.formatted_name = ''
        self.name_parts = ''

    def check_version(self, fields, data):
        """Check the version of the VCard, only version 3.0 is supported."""
        self.version = data
        if self.version != "3.0":
            raise GrampsImportError(
                _("Import of VCards version %s is "
                  "not supported by Gramps.") % self.version)

    def add_formatted_name(self, fields, data):
        """Read the FN property of a VCard."""
        if not self.formatted_name:
            self.formatted_name = self.unesc(str(data)).strip()

    def add_name_parts(self, fields, data):
        """Read the N property of a VCard."""
        if not self.name_parts:
            self.name_parts = data.strip()

    def add_name(self):
        """
        Add the name to the person.

        Returns True on success, False on failure.
        """
        if not self.name_parts.strip():
            self.__add_msg(
                _("VCard is malformed missing the compulsory N "
                  "property, so there is no name; skip it."),
                self.line_num - 1)
            return False
        if not self.formatted_name:
            self.__add_msg(
                _("VCard is malformed missing the compulsory FN "
                  "property, get name from N alone."), self.line_num - 1)
        data_fields = self.split_unescaped(self.name_parts, ';')
        if len(data_fields) != 5:
            self.__add_msg(
                _("VCard is malformed wrong number of name "
                  "components."), self.line_num - 1)

        name = Name()
        name.set_type(NameType(NameType.BIRTH))

        if data_fields[0].strip():
            # assume first surname is primary
            for surname_str in self.split_unescaped(data_fields[0], ','):
                surname = Surname()
                prefix, sname = splitof_nameprefix(self.unesc(surname_str))
                surname.set_surname(sname.strip())
                surname.set_prefix(prefix.strip())
                name.add_surname(surname)
            name.set_primary_surname()

        if len(data_fields) > 1 and data_fields[1].strip():
            given_name = ' '.join(
                self.unesc(self.split_unescaped(data_fields[1], ',')))
        else:
            given_name = ''
        if len(data_fields) > 2 and data_fields[2].strip():
            additional_names = ' '.join(
                self.unesc(self.split_unescaped(data_fields[2], ',')))
        else:
            additional_names = ''
        self.add_firstname(given_name.strip(), additional_names.strip(), name)

        if len(data_fields) > 3 and data_fields[3].strip():
            name.set_title(' '.join(
                self.unesc(self.split_unescaped(data_fields[3], ','))))
        if len(data_fields) > 4 and data_fields[4].strip():
            name.set_suffix(' '.join(
                self.unesc(self.split_unescaped(data_fields[4], ','))))

        self.person.set_primary_name(name)
        return True

    def add_firstname(self, given_name, additional_names, name):
        """
        Combine given_name and additional_names and add as firstname to name.

        If possible try to add given_name as call name.
        """
        default = "%s %s" % (given_name, additional_names)
        if self.formatted_name:
            if given_name:
                if additional_names:
                    given_name_pos = self.formatted_name.find(given_name)
                    if given_name_pos != -1:
                        add_names_pos = self.formatted_name.find(
                            additional_names)
                        if add_names_pos != -1:
                            if given_name_pos <= add_names_pos:
                                firstname = default
                                # Uncertain if given name is used as callname
                            else:
                                firstname = "%s %s" % (additional_names,
                                                       given_name)
                                name.set_call_name(given_name)
                        else:
                            idx = fitin(self.formatted_name, additional_names,
                                        given_name)
                            if idx == -1:
                                # Additional names is not in formatted name
                                firstname = default
                            else:  # Given name in middle of additional names
                                firstname = "%s%s %s" % (
                                    additional_names[:idx], given_name,
                                    additional_names[idx:])
                                name.set_call_name(given_name)
                    else:  # Given name is not in formatted name
                        firstname = default
                else:  # There are no additional_names
                    firstname = given_name
            else:  # There is no given_name
                firstname = additional_names
        else:  # There is no formatted name
            firstname = default
        name.set_first_name(firstname.strip())
        return

    def add_nicknames(self, fields, data):
        """Read the NICKNAME property of a VCard."""
        for nick in self.split_unescaped(data, ','):
            nickname = nick.strip()
            if nickname:
                name = Name()
                name.set_nick_name(self.unesc(nickname))
                self.person.add_alternate_name(name)

    def add_sortas(self, fields, data):
        """Read the SORT-STRING property of a VCard."""
        # TODO
        pass

    def add_address(self, fields, data):
        """Read the ADR property of a VCard."""
        data_fields = self.split_unescaped(data, ';')
        data_fields = [x.strip() for x in self.unesc(data_fields)]
        if ''.join(data_fields):
            addr = Address()

            def add_street(strng):
                if strng:
                    already = addr.get_street()
                    if already:
                        addr.set_street("%s %s" % (already, strng))
                    else:
                        addr.set_street(strng)

            addr.add_street = add_street
            set_func = [
                'add_street', 'add_street', 'add_street', 'set_city',
                'set_state', 'set_postal_code', 'set_country'
            ]
            for i, data in enumerate(data_fields):
                if i >= len(set_func):
                    break
                getattr(addr, set_func[i])(data)
            self.person.add_address(addr)

    def add_phone(self, fields, data):
        """Read the TEL property of a VCard."""
        tel = data.strip()
        if tel:
            addr = Address()
            addr.set_phone(self.unesc(tel))
            self.person.add_address(addr)

    def add_birthday(self, fields, data):
        """Read the BDAY property of a VCard."""
        date_str = data.strip()
        date_match = VCardParser.DATE_RE.match(date_str)
        date = Date()
        if date_match:
            if date_match.group(2):
                date_str = "%s-%s-%s" % (date_match.group(2),
                                         date_match.group(3),
                                         date_match.group(4))
            else:
                date_str = date_match.group(1)
            y, m, d = [int(x, 10) for x in date_str.split('-')]
            try:
                date.set(value=(d, m, y, False))
            except DateError:
                # TRANSLATORS: leave the {vcard_snippet} untranslated
                # in the format string, but you may re-order it if needed.
                self.__add_msg(
                    _("Invalid date in BDAY {vcard_snippet}, "
                      "preserving date as text.").format(vcard_snippet=data),
                    self.line_num - 1)
                date.set(modifier=Date.MOD_TEXTONLY, text=data)
        else:
            if date_str:
                # TRANSLATORS: leave the {vcard_snippet} untranslated.
                self.__add_msg(
                    _("Date {vcard_snippet} not in appropriate format "
                      "yyyy-mm-dd, preserving date as text.").format(
                          vcard_snippet=date_str), self.line_num - 1)
                date.set(modifier=Date.MOD_TEXTONLY, text=date_str)
            else:  # silently ignore an empty BDAY record
                return
        event = Event()
        event.set_type(EventType(EventType.BIRTH))
        event.set_date_object(date)
        self.database.add_event(event, self.trans)

        event_ref = EventRef()
        event_ref.set_reference_handle(event.get_handle())
        self.person.set_birth_ref(event_ref)

    def add_occupation(self, fields, data):
        """Read the ROLE property of a VCard."""
        occupation = data.strip()
        if occupation:
            event = Event()
            event.set_type(EventType(EventType.OCCUPATION))
            event.set_description(self.unesc(occupation))
            self.database.add_event(event, self.trans)

            event_ref = EventRef()
            event_ref.set_reference_handle(event.get_handle())
            self.person.add_event_ref(event_ref)

    def add_url(self, fields, data):
        """Read the URL property of a VCard."""
        href = data.strip()
        if href:
            url = Url()
            url.set_path(self.unesc(href))
            self.person.add_url(url)

    def add_email(self, fields, data):
        """Read the EMAIL property of a VCard."""
        email = data.strip()
        if email:
            url = Url()
            url.set_type(UrlType(UrlType.EMAIL))
            url.set_path(self.unesc(email))
            self.person.add_url(url)

    def add_gender(self, fields, data):
        """Read the GENDER property of a VCard."""
        gender_value = data.strip()
        if gender_value:
            gender_value = gender_value.upper()
            gender_value = gender_value[0]
            if gender_value == 'M':
                gender = Person.MALE
            elif gender_value == 'F':
                gender = Person.FEMALE
            else:
                return
            self.person.set_gender(gender)