Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 31 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,55 +1,64 @@
# csv2vcf
csv2vcf is a small command line tool to convert CSV files to VCard (.vcf) files.

## Usage :
## Usage

Go to terminal or command prompt and type :

```
python csv2vcf.py CSV_FILE_NAME [ -s | --single ] INPUT_FILE_FORMAT
```

Where :
Where

- `CSV_FILE_NAME` is the full name of the CSV file you want to convert
- `INPUT_FILE_FORMAT` is a JSON formatted string which tells **csv2vcf** how to parse your input file
- `-s` or `--single` : use this argument if you want your output in a single file. Optional parameter. By default, the program will create separate Vcard files for each entry

### JSON Format

The JSON string can have the following keys ([vCard property types](https://en.wikipedia.org/wiki/VCard)) :

###### JSON Format :
| key titles |
|------------|
| first_name |
| last_name |
| full_name |
| nickname |
| tel |
| email |
| org |
| url |
| bday |
| role |

The JSON string can have the following keys :
Format is `{KEY_1:KEY_1_TITLE, KEY_2:KEY_2_TITLE, ...}`

`name`, `nickname`, `org`, `tel`, `url`, `bday`, `role`, and `email`, where each property is in accordance with [vCard property types](https://en.wikipedia.org/wiki/VCard)

Format is `{KEY_1:KEY_1_COLUMN_NO, KEY_2:KEY_2_COLUMN_NO, ...}`


###### Example :
###### Example

Suppose you have a CSV file `contacts.csv` with the following content :

```
+-----------+-------------+
| NAME | MOBILE |
+-----------+-------------+
| Mrid | 1111111111 |
| Arnav | 2222222222 |
| Sunil | 3333333333 |
| . | . |
| . | . |
| . | . |
+-----------+-------------+
+-----------+--------------+
| NAME | MOBILE PHONE |
+-----------+--------------+
| Mrid | 1111111111 |
| Arnav | 2222222222 |
| Sunil | 3333333333 |
| . | . |
| . | . |
| . | . |
+-----------+--------------+
```

To convert this file to vCard, you will have to write :
To convert this file to vCard, you will have to write:

`python csv2vcf.py contacts.csv '{"name":1, "tel":2}'`
`python csv2vcf.py contacts.csv '{"full_name": "NAME", "tel": "MOBILE PHONE"}'`

if you want separate vCards for each person, or

`python csv2vcf.py contacts.csv -s '{"name":1, "tel":2}'`
`python csv2vcf.py contacts.csv -s '{"full_name": "NAME", "tel": "MOBILE PHONE"}'`

if you want to generate a single vCard file

Expand Down
155 changes: 94 additions & 61 deletions csv2vcf.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,28 +10,51 @@
import csv
import json

default_input_file_format = {
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This aids readability, makes the supported keys visible

'first_name': None,
'last_name': None,
'full_name': None,
'nickname': None,
'org': None,
'tel': None,
'url': None,
'bday': None,
'role': None,
'email': None,
'note': None
}


def convert_to_vcard(input_file, single_output, input_file_format):

FN = input_file_format['name']-1 if 'name' in input_file_format else None
NICKNAME = input_file_format['nickname']-1 if 'nickname' in input_file_format else None
ORG = input_file_format['org']-1 if 'org' in input_file_format else None
TEL = input_file_format['tel']-1 if 'tel' in input_file_format else None
URL = input_file_format['url']-1 if 'url' in input_file_format else None
BDAY = input_file_format['bday']-1 if 'bday' in input_file_format else None
ROLE = input_file_format['role']-1 if 'role' in input_file_format else None
EMAIL = input_file_format['email']-1 if 'email' in input_file_format else None
NOTE = input_file_format['note']-1 if 'note' in input_file_format else None
input_file_format = {**default_input_file_format, **input_file_format}

FIRST_NAME = input_file_format['first_name']
LAST_NAME = input_file_format['last_name']
FULL_NAME = input_file_format['full_name']

NICKNAME = input_file_format['nickname']

ORG = input_file_format['org']
TEL = input_file_format['tel']
URL = input_file_format['url']
BDAY = input_file_format['bday']
ROLE = input_file_format['role']
EMAIL = input_file_format['email']
NOTE = input_file_format['note']

# if single output option is selected
if single_output :
with open( input_file, 'r' ) as source_file:
reader = csv.reader( source_file )
if single_output:
with open(input_file, 'r', encoding='utf-8-sig') as source_file:
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removes the BOM character prefix if exists

reader = csv.DictReader(source_file)
Copy link
Copy Markdown
Author

@sjdonado sjdonado Apr 15, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The csv file generated by Outlook contains tons keys:

First Name | Middle Name | Last Name | Title | Suffix | Nickname | Given Yomi | Surname Yomi | E-mail Address | E-mail 2 Address | E-mail 3 Address | Home Phone | Home Phone 2 | Business Phone | Business Phone 2 | Mobile Phone | Car Phone | Other Phone | Primary Phone | Pager | Business Fax | Home Fax | Other Fax | Company Main Phone | Callback | Radio Phone | Telex | TTY/TDD Phone | IMAddress | Job Title | Department | Company | Office Location | Manager's Name | Assistant's Name | Assistant's Phone | Company Yomi | Business Street | Business City | Business State | Business Postal Code | Business Country/Region | Home Street | Home City | Home State | Home Postal Code | Home Country/Region | Other Street | Other City | Other State | Other Postal Code | Other Country/Region | Notes | Personal Web Page | Spouse | Schools | Hobby | Location | Web Page | Birthday | Anniversary

Indexing by key titles is more convenient

single_vcf = open('csv2vcf/all_contacts.vcf', 'w')
i = 0
for row in reader:

FN_VAL = row[FN] if FN is not None else ''
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

new logic for building the FN_VAL based on the full_name or first_name + last_name pair

FN_VAL = row[FULL_NAME] if FULL_NAME is not None else ''
FN_VAL = row[FIRST_NAME] + ' ' + \
row[LAST_NAME] if FN_VAL == '' else FN_VAL

NICKNAME_VAL = row[NICKNAME] if NICKNAME is not None else ''
ORG_VAL = row[ORG] if ORG is not None else ''
TEL_VAL = row[TEL] if TEL is not None else ''
Expand All @@ -57,20 +80,20 @@ def convert_to_vcard(input_file, single_output, input_file_format):
print('----------------------')

# write the single file
single_vcf.write( 'BEGIN:VCARD' + "\n")
single_vcf.write( 'VERSION:3.0' + "\n")
single_vcf.write( 'N:' + FN_VAL + ';' + "\n")
single_vcf.write( 'FN:' + FN_VAL + "\n")
single_vcf.write( 'NICKNAME:' + NICKNAME_VAL + "\n")
single_vcf.write( 'TEL;HOME;VOICE:' + TEL_VAL + "\n")
single_vcf.write( 'EMAIL:' + EMAIL_VAL + "\n")
single_vcf.write( 'BDAY:' + BDAY_VAL + "\n")
single_vcf.write( 'ORG:' + ORG_VAL + "\n")
single_vcf.write( 'ROLE:' + ROLE_VAL + "\n")
single_vcf.write( 'URL:' + URL_VAL + "\n")
single_vcf.write( 'NOTE:' + NOTE_VAL + "\n")
single_vcf.write( 'END:VCARD' + "\n")
single_vcf.write( "\n")
single_vcf.write('BEGIN:VCARD' + "\n")
single_vcf.write('VERSION:3.0' + "\n")
single_vcf.write('N:' + FN_VAL + ';' + "\n")
single_vcf.write('FN:' + FN_VAL + "\n")
single_vcf.write('NICKNAME:' + NICKNAME_VAL + "\n")
single_vcf.write('TEL;HOME;VOICE:' + TEL_VAL + "\n")
single_vcf.write('EMAIL:' + EMAIL_VAL + "\n")
single_vcf.write('BDAY:' + BDAY_VAL + "\n")
single_vcf.write('ORG:' + ORG_VAL + "\n")
single_vcf.write('ROLE:' + ROLE_VAL + "\n")
single_vcf.write('URL:' + URL_VAL + "\n")
single_vcf.write('NOTE:' + NOTE_VAL + "\n")
single_vcf.write('END:VCARD' + "\n")
single_vcf.write("\n")

i += 1

Expand All @@ -79,13 +102,16 @@ def convert_to_vcard(input_file, single_output, input_file_format):
print('----------------------')

# default ( multi-file output )
else :
with open( input_file, 'r' ) as source_file:
reader = csv.reader( source_file )
else:
with open(input_file, 'r', encoding='utf-8-sig') as source_file:
reader = csv.DictReader(source_file)
i = 0
for row in reader:

FN_VAL = row[FN] if FN is not None else ''
FN_VAL = row[FULL_NAME] if FULL_NAME is not None else ''
FN_VAL = row[FIRST_NAME] + ' ' + \
row[LAST_NAME] if FN_VAL == '' else FN_VAL

NICKNAME_VAL = row[NICKNAME] if NICKNAME is not None else ''
ORG_VAL = row[ORG] if ORG is not None else ''
TEL_VAL = row[TEL] if TEL is not None else ''
Expand All @@ -111,20 +137,21 @@ def convert_to_vcard(input_file, single_output, input_file_format):
print('----------------------')

# write each entry
each_vcf = open('csv2vcf/' + FN_VAL + '_' + TEL_VAL + ".vcf", 'w')
each_vcf.write( 'BEGIN:VCARD' + "\n")
each_vcf.write( 'VERSION:3.0' + "\n")
each_vcf.write( 'N:' + FN_VAL + ';' + "\n")
each_vcf.write( 'FN:' + FN_VAL + "\n")
each_vcf.write( 'NICKNAME:' + NICKNAME_VAL + "\n")
each_vcf.write( 'TEL;HOME;VOICE:' + TEL_VAL + "\n")
each_vcf.write( 'EMAIL:' + EMAIL_VAL + "\n")
each_vcf.write( 'BDAY:' + BDAY_VAL + "\n")
each_vcf.write( 'ORG:' + ORG_VAL + "\n")
each_vcf.write( 'ROLE:' + ROLE_VAL + "\n")
each_vcf.write( 'URL:' + URL_VAL + "\n")
each_vcf.write( 'NOTE:' + NOTE_VAL + "\n")
each_vcf.write( 'END:VCARD' + "\n")
each_vcf = open('csv2vcf/' + FN_VAL +
'_' + TEL_VAL + ".vcf", 'w')
each_vcf.write('BEGIN:VCARD' + "\n")
each_vcf.write('VERSION:3.0' + "\n")
each_vcf.write('N:' + FN_VAL + ';' + "\n")
each_vcf.write('FN:' + FN_VAL + "\n")
each_vcf.write('NICKNAME:' + NICKNAME_VAL + "\n")
each_vcf.write('TEL;HOME;VOICE:' + TEL_VAL + "\n")
each_vcf.write('EMAIL:' + EMAIL_VAL + "\n")
each_vcf.write('BDAY:' + BDAY_VAL + "\n")
each_vcf.write('ORG:' + ORG_VAL + "\n")
each_vcf.write('ROLE:' + ROLE_VAL + "\n")
each_vcf.write('URL:' + URL_VAL + "\n")
each_vcf.write('NOTE:' + NOTE_VAL + "\n")
each_vcf.write('END:VCARD' + "\n")
each_vcf.write("\n")
each_vcf.close()

Expand All @@ -137,44 +164,50 @@ def convert_to_vcard(input_file, single_output, input_file_format):
def main(args):
args_len = len(args)

if args_len < 3 or args_len > 4 :
print ( "Usage:")
print ( args[0] + " filename")
if args_len < 3 or args_len > 4:
print("Usage:")
print(args[0] + " filename")
sys.exit()

if args_len == 3 :
if args_len == 3:
input_file = args[1]
try :

try:
input_file_format = json.loads(args[2])
except Exception as e :
except Exception:
print('\033[91m'+"ERROR : json could not be parsed"+'\033[0m')
sys.exit()

single_output = 0
elif args_len == 4 :
elif args_len == 4:
input_file = args[1]

if args[2] == '-s' or args[2] == '--single' :
if args[2] == '-s' or args[2] == '--single':
single_output = 1
else :
print('\033[91m'+"ERROR : invalid argument `" + args[2] + "`"+'\033[0m')
else:
print('\033[91m'+"ERROR : invalid argument `" +
args[2] + "`"+'\033[0m')
sys.exit()

try :
try:
input_file_format = json.loads(args[3])
except Exception as e :
except Exception:
print('\033[91m'+"ERROR : json could not be parsed"+'\033[0m')
sys.exit()

if not os.path.exists(input_file) :
print('\033[91m'+"ERROR : file `" + input_file + "` not found"+'\033[0m')
if not os.path.exists(input_file):
print('\033[91m'+"ERROR : file `" +
input_file + "` not found"+'\033[0m')
sys.exit()

if not os.path.exists('csv2vcf') :
if not os.path.exists('csv2vcf'):
os.makedirs('csv2vcf')

convert_to_vcard(input_file, single_output, input_file_format)

print('\033[92m'+"DONE"+'\033[0m')
print("Output files are in `csv2vcf` folder")
Copy link
Copy Markdown
Author

@sjdonado sjdonado Apr 15, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when generating output files it is always useful to print the path



if __name__ == '__main__':
main(sys.argv)