Convert *.VCF contact file for cellphone import My new mobile phone is capable of importing contact information from a renamed *.VCF file stored on an SD memory card (https://www.quora.com/How-do-I-sync-contacts-on-Nokia-130). Annoyingly, it does not work very well for contacts with multiple phone numbers or anything not tagged as TEL: or TEL;TYPE=CELL:.

The following script takes an *.vcf file, parses the stored contact information as needed - first name, last name and phone numbers - and outputs the information in a way that the phone imports it correctly. Therefore, each contact may only contain one number. For multiple numbers, a contact will be duplicated for each phone number. The resulting string is simply printet out.

#!/usr/bin/python

import re


class Contact(object):
    """
    Contains the contact infromation,
    including a list of phone numbers.

    Arguments:
        object {[type]} -- [description]
    """

    def __init__(self):
        self.FirstName = ""
        self.LastName = ""
        self.Phonenumbers = []


def extract_number(line):
    """
    Extracts the phone number from a vCard-file line,
    by removing everything but numbers and '+'

    Arguments:
        line {string} -- the line to extract the phone number from

    Returns:
        string -- the phone number
    """

    line = line[line.index(":")+1:].rstrip()
    line = re.sub('[^0-9+]', '', line)
    return line


def generate_vcard_contact_string(contact):
    """
    Generates the vCard string for this contact.
    Will generate a sperate vCard for each phone number of the contact.

    Arguments:
        contact {Contact} -- the contact to generate the vCard string from

    Returns:
        string -- the generated vCard string
    """

    base = f"BEGIN:VCARD\n"
    base += f"N:{contact.LastName};{contact.FirstName}\n"
    base += f"TEL:{{phone_number}}\n"  # use '{phone_number}' as a placeholder.
    # Will be replaced by actual number.
    base += f"END:VCARD\n"

    result = ""
    for number in contact.Phonenumbers:
        # Add the base string for this contact,
        # replace {phone_number} with actual number
        result += base.replace("{phone_number}", number)

    return result


contacts = []

with open(r'contacts.vcf') as f:
    current_contact = Contact()
    for line in f:
        # Some lines are build like "item1.TEL;...",
        # remove "item1.", "item2.", to ease parsing
        if line.startswith("item"):
            line = line[line.index(".")+1:]

        if "BEGIN:VCARD" in line:
            # Marks the start of a vCard,
            # create a blank contact to work with
            current_contact = Contact()

        elif line.startswith("N:"):
            # Line contains a name in the format N:LastName;FirstName;...;...;
            # Only LastName and FirstName will be used
            line = line.replace("N:", "")  # remove "N:" from line
            chunks = line.split(';')
            current_contact.LastName = chunks[0].strip()
            current_contact.FirstName = chunks[1].strip()

        elif line.startswith("TEL"):
            # Line contains a phone number
            # phone number type may be specified by "TYPE=...",
            # currently, TYPE will ne omitted
            # One contact may contain multiple phone numbers.
            number = extract_number(line)
            current_contact.Phonenumbers.append(number)

        elif "END:VCARD" in line:
            # Marks the end of a vCard,
            # append contact to list
            contacts.append(current_contact)

result = ""
for contact in contacts:
    # Generate a string containing a vCard for each of the contacts phone
    # numbers, append those string to create one big text containing everything
    result += generate_vcard_contact_string(contact)

print(result)
Your function generate_vcard_contact_string obviously belongs to the Contact class. It even takes a Contact instance as the first argument.

I would actually elevate this to be the magic __str__ method, which is for example called when you do print(contact):

class Contact(object):
    """
    Contains the contact information,
    including a list of phone numbers.

    Arguments:
        object {[type]} -- [description]
    """

    def __init__(self):
        self.FirstName = ""
        self.LastName = ""
        self.Phonenumbers = []

    def __str__(self):
        """
        Generates the vCard string for this contact.
        Will generate a separate vCard for each phone number of the contact.

        Returns:
            string -- the generated vCard string
        """

        base = "\n".join(["BEGIN:VCARD",
                          f"N:{self.LastName};{self.FirstName}",
                          "TEL:{}",
                          "END:VCARD"])

        return "".join(base.format(number) for number in self.Phonenumbers)


You should also place your calling code under a if __name__ == "__main__": guard to allow importing from other scripts without executing the code. It is also complex enough to be encapsulated in its own function, IMO:

def parse_contacts(file_name):
    with open(file_name) as f:
        contact = None
        for line in f:
            # Some lines are build like "item1.TEL;...",
            # remove "item1.", "item2.", to ease parsing
            if line.startswith("item"):
                line = line[line.index(".")+1:]

            if "BEGIN:VCARD" in line:
                # Marks the start of a vCard,
                # create a blank contact to work with
                contact = Contact()

            elif line.startswith("N:"):
                # Line contains a name in the format N:LastName;FirstName;...;...;
                # Only LastName and FirstName will be used
                chunks = line[2:].split(';')
                contact.LastName = chunks[0].strip()
                contact.FirstName = chunks[1].strip()

            elif line.startswith("TEL:"):
                # Line contains a phone number
                # phone number type may be specified by "TYPE=...",
                # currently, TYPE will ne omitted
                # One contact may contain multiple phone numbers.
                number = extract_number(line)
                contact.Phonenumbers.append(number)

            elif "END:VCARD" in line:
                # Marks the end of a vCard,
                yield contact

def main():
    for contact in parse_contacts('contacts.vcf'):
        print(contact)

if __name__ == "__main__":
    main()


Your extract_number function can also be simplified using a single regex:

def extract_number(line):
    """
    Extracts the phone number from a vCard-file line,
    by removing everything but numbers and '+'

    Arguments:
        line {string} -- the line to extract the phone number from

    Returns:
        string -- the phone number
    """
    return re.match(r'TEL:(\+?\d*)', line).groups()[0]