Skip navigation.
Home
Now Shipping Version 7.0

IMPORTING EVOLUTION CONTACTS INTO THUNDERBIRD'S ADDRESSBOOK


Posts: 21
NOTE: (You MUST have python installed to do this)

1. Copy the script below to an empty ascii file and save it as:
'evol2tbird-addressbook.py'

2. Put it in an empty folder you create for this process.

3. Make the file executable:

chmod +x evol2tbird-addressbook.py

4. Run the program to export the addressbook file from Evolution:

./evol2tbird-addressbook.py --db [path to evolution's addressbook.db]

ex.: ./evol2tbird-addressbook.py --db ~/evolution/local/Contacts/addressbook.db > adbook.txt

5. Now open the addressbook in Thunderbird and select 'Tools/Import'

6. Work through the dialogs until you point to the file the script created ... 'adbook.txt'

7. Select that file and click 'OK' - then select what info you want imported and select 'OK' again.

8. Your Evolution Addressbook is now imported into Thunderbird.

--MepisBelle

%< cut ------------------------------------------------------------

This script was created by Todd Warner

Copyright (C) 2004 Todd Warner - All rights reserved.

We thank him for sharing it with the community.

The script follows ... be sure to copy and paste everything below the next line:

--------------------------------------------------------------------

#!/usr/bin/python -u
""" evolution --> thunderbird addressbook migration script

Known to work with Ximian Evolution 1.2 and Mozilla Thunderbird 0.6.
Or right about 2004-06-05.

Both products are mail clients for Linux and other platforms. Both rock.

This utility was written because Ximian Evolution (1.2) does not export its
address book very well to other programs in general, and Mozilla
Thunderbird (0.6) in particular. Both programs are at fault I think.

I googled for a solution and got a half-baked python & perl script combo
that did some of the job. Since I was not satisfied with that, I decided
to spend a bit of time and write a more robust utility myself. I.e., it
was one of those one-off programs that ended up expanding and taking way
too much of my time!

USAGE:
./evol2tbird-addressbook.py --db [path to evolution's addressbook.db]
or
./evol2tbird-addressbook.py --vcard [path to evolution's exported vcards]

(if no options are used the default is
--db ~/evolution/local/Contacts/addressbook.db)

How it works:
o I iterate over the addressbook records in Evolution's bsddb Contacts
database.
-or-
I iterate over the VCard records in a text file that Evolution exported.
o as I pull in data, I translate that data into strings that are comma
deliminated - Comma Seperated Values, or CSV.
o this cvs output is *exactly* formated to what the Thunderbird mail
client expects.

What would be better:
o Evolution needs to be able to convert the data to a more robust portable
(and *sigh* more complex) format such as LDAP Data Interchange Format
(LDIF) that anyone can parse. I am certain it is on their todo list.
o Thunderbird needs to be able to import Evolution's exported VCards
natively. Evolution is extremely popular. It only makes sense.
o This utility would be better if it translated Evolution's VCard format
to LDIF. Maybe I will do that one of these days. Don't hold your breath
though. Eye-wink

License: CC-GNU LGPL v2.1

Copyright (C) 2004 Todd Warner <taw{at}pobox.com>
All rights reserved.

Author: Todd Warner <taw{at}pobox.com>

See also:
Ximian (Novell) Evolution: http://www.novell.com/products/evolution/
Mozilla Thunderbird: http://www.mozilla.org/projects/thunderbird/
CC-GNU LGPL v2.1: http://creativecommons.org/licenses/LGPL/2.1/
LDIF: http://whatis.techtarget.com/definition/0,,sid9_gci549219,00.html
VCard: http://www.free-definition.com/VCard.html
"""
#------------------------------------------------------------------------------
# $Id: evol2tbird-addressbook.py,v 1.5 2004/06/09 04:54:55 taw Exp $

import os
import re
import sys
import bsddb
import string

def cleanupAbsPath(path):
""" take ~taw/../some/path/$MOUNT_POINT/blah and make it sensible.

Path return is absolute.
NOTE: python 2.2 fixes a number of bugs with this and eliminates
the need for os.path.expanduser
"""

if path is None:
return None
return os.path.abspath(
os.path.expanduser(
os.path.expandvars(path)))

DB_PATH = "~/evolution/local/Contacts/addressbook.db"

class DB:

def __init__(self, dbpath):
self.dbpath = dbpath
self.open()
self._bsddb = bsddb # a hack for python 1.5.2's gc so that
# the __del__ method works w/o fail.

def __del__(self):
self.close()

def open(self):
self.db = bsddb.hashopen(self.dbpath, 'r')

def close(self):
try:
self.db.close()
except:
pass

def findAndStrip(s, key):
s = string.strip(s)
#print 'findAndStrip', string.find(s, key), s, key
if string.find(s, key) == 0:
return string.replace(s, key, '', 1)
raise KeyError("not found")

# Format of a single csv entry:
# "first,last,full,nick,email1,email2,workph,homeph,fax,pager,\
# cell,hadd1,hadd2,hcity,hstate,hzip,hcountry,wadd1,wadd2,wcity,\
# wstate,wzip,wcountry,title,org-unit,org,wurl,hurl,birthY,birthM,\
# birthD,custom1,custom2,custom3,custom4,notes1,"
#
# Note the 3 ?'s that I don't know what are for.
# I believe there needs to be at least 36 items in that list (36 commas)

# Possible keys in the vcard (order is important!)
_EVOL_KEYS = ['N:', 'FN:', 'NICKNAME:', 'EMAIL;INTERNET:',
'TEL;WORK;VOICE:', 'TEL;HOME:', 'TEL;WORK;FAX:',
'TEL;PAGER:', 'TEL;CELL:',
'LABEL;QUOTED-PRINTABLE;HOME:',
'LABEL;QUOTED-PRINTABLE;WORK;PREF:',
'TITLE:', 'ORG:', 'URL:',
# The next three are not exported by evolution
# but are used by the imported and must be in here.
'birth-year', 'birth-month', 'birth-day',
'NOTE;QUOTED-PRINTABLE:']

def balanceQuotes(s):
listYN = type(s) == type([])
if not listYN:
s = [s]
for i in range(len(s)):
s[i] = string.strip(s[i])
if s[i] and len(s[i]) > 1:
if s[i][0] == '"' and s[i][-1] != '"':
s[i] = s[i] + '"'
elif s[i][0] != '"' and s[i][-1] == '"':
s[i] = '"' + s[i]
if not listYN:
return s[0]
return s

def splitStrip(s, spl=''):
s = string.split(s, spl)
for i in range(len(s)):
s[i] = string.strip(s[i])
return s

def splitStripBalance(s, slp=''):
return balanceQuotes(string.split(s, slp))

def figureFirstLast(n):
""" ["warner;todd"] --> "todd,warner" """
if not n:
return ','
n = splitStripBalance(n[0], ';')
if not n:
return ','
if len(n) == 1:
return n[0]+','
return n[1]+','+n[0]

def figureEmail(e):
""" [email, email] --> "email,email" """
e = balanceQuotes(e)
if not e:
return ','
elif len(e) == 1:
return e[0]+','
# return at most two email addresses
return e[0] + ',' + e[1]

def figureAddress(a):
""" ["line1=0Aline2"] --> "line1,line2,,,," """
if not a:

a = ['']
a = splitStripBalance(a[0], '=0A')
a = a + ['']*(6-len(a)) # 6 fields
a = a[:6]
a = string.join(a, ',')
return a

def figureURL(u):
""" ["URL","URL"] --> "URL,URL,,," """
# only 2 URLs allowed
u = u + ['']*(2-len(u))
u = u[:2]
return string.join(balanceQuotes(u), ',')

def figureOrg(o):
""" ["ORG","ORG-UNIT"] --> "ORG-UNIT,ORG" """
if not o:
return ','
o = string.split(o[0], ';')
# vcard has them flip-flopped (org,org-unit)
if len(o) > 1:
return o[1] + ',' + o[0]
else:
return o[0] + ','
return ','

def figureNotes(n):
""" "cnote1=0Acnote2=0Acnote3=0Acnote4=0Anote"
--> "cnote1,cnote2,cnote3,cnote4,Anote Anote-continued"

You can have 4 custom notes and 1 general note.
The general note is space deliminated.
"""
cust = []
note = []
for each in n:
each = splitStripBalance(each, '=0A')
l = max(4-len(cust), 0)
cust = cust + each[:l]
note = note + each[l:]
cust = cust + ['']*(4-len(cust)) # must have at least 4
cust = string.join(cust, ',')
note = string.join(note, ' ') or ''
return cust + ',' + note

def figureOther(o):
""" ["xxx","yyy"] --> "xxx,yyy" """
if not o:
return ''
o = balanceQuotes(o)
return string.join(o, ',')

def processVCard(vcard):
d = {}
mutexish = 0
for line in vcard:
# begin check - are we in the vcard yet?
# is this a vcard?
if not mutexish:
try:
findAndStrip(line, 'BEGIN:VCARD')
mutexish = 1
continue
except KeyError:
continue

# end check - if you found the end, break the loop
if string.find(line, 'END:VCARD') == 0:
break

# key processing
for key in _EVOL_KEYS:
if string.split(line, ':')[0]+':' not in _EVOL_KEYS:
break
try:
x = findAndStrip(line, key)
# if we find a comma, we need to quote the whole string.
if string.count(x, ','):
if x[0] != '"':
x = '"' + x
if x[-1] != '"':
x = x + '"'
# more than one! Append!
if d.has_key(key):
d[key].append(x)
else:
d[key] = [x]
break
except KeyError:
pass
return d

def cvs(d):
""" cvs-ify a parsed vcard """

s = ''
for k in _EVOL_KEYS:
if k == 'N:':
s = s + figureFirstLast(d.get(k, [])) + ','
elif k == 'EMAIL;INTERNET:':
s = s + figureEmail(d.get(k, [])) + ','
elif k in ('LABEL;QUOTED-PRINTABLE;HOME:',
'LABEL;QUOTED-PRINTABLE;WORK;PREF:'):
s = s + figureAddress(d.get(k, [])) + ','
elif k == 'URL:':
s = s + figureURL(d.get(k, [])) + ','
elif k == 'birth-year': # not exported by evolution
s = s + ','
elif k == 'birth-month': # not exported by evolution
s = s + ','
elif k == 'birth-day': # not exported by evolution
s = s + ','
elif k == 'ORG:':
s = s + figureOrg(d.get(k, [])) + ','
elif k == 'NOTE;QUOTED-PRINTABLE:':
s = s + figureNotes(d.get(k, [])) + ','
else:
# everything else
s = s + figureOther(d.get(k, [])) + ','
return s

def export(db):
db = db.db
for k in db.keys():
print cvs(processVCard(db[k].split('\n')))
#sys.exit(999)

def usage():
executable = os.path.basename(sys.argv[0])
print """\
USAGE:
%s --db [path to addressbook.db]
or
%s --vcard [path to an evolution exported *.vcf file]

NOTE: if no options are used the default option and value is:
--db %s
""" % (executable, executable, DB_PATH)
sys.exit(-1)

def processCommandLine(argv):
""" chose to *not* import optik or getopt just to be simple """

if len(argv) > 3 or '-h' in argv or '--help' in argv:
usage()

if len(argv) == 2:
usage()

if len(argv) == 1:
argv = argv + ['--db', DB_PATH]

if argv[1] not in ['--db', '--vcard']:
usage()

dbpath = None
vcardpath = None
if argv[1] == '--db':
dbpath = cleanupAbsPath(argv[2])
elif argv[1] == '--vcard':
vcardpath = cleanupAbsPath(argv[2])

if not os.path.exists(dbpath or vcardpath):
sys.stderr.write("ERROR: file doesn't exist: %s\n"
% (dbpath or vcardpath))
sys.exit(-1)

if not os.path.isfile(dbpath or vcardpath):
sys.stderr.write("ERROR: not a file: %s\n"
% (dbpath or vcardpath))
sys.exit(-1)

return dbpath, vcardpath

def dosStrip(s):
if not s:
return s
if s[-1] == '\n':
s = s[:-1]
if not s:
return s
if s[-1] == '\r':
s = s[:-1]
s = string.strip(s)
return s

#------------------------------------------------------------------------------

def main(argv=sys.argv):
dbpath, vcardpath = processCommandLine(argv)
if dbpath:
db = DB(dbpath)
for k in db.db.keys():
print cvs(processVCard(db.db[k].split('\n')))
else:
fo = open(vcardpath, 'rb')
line = fo.readline()
while line:
vcard = []
if string.find(line, 'BEGIN:VCARD') == 0:
while 1:
vcard.append(dosStrip(line))
if string.find(line, 'END:VCARD') == 0:
break
line = fo.readline()
print cvs(processVCard(vcard))
line = fo.readline()

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

if __name__ == '__main__':
sys.exit(main() or 0)

#==============================================================================

EditDeleteReply

Hi I tried running your scr

Hi I tried running your script, as myself and as root, when I do I get the following error

File "./evol2tbird-addressbook.py", line 77
"""
^
IndentationError: expected an indented block

I am running Evolution 2.0.0 on Slackware 10, With Python 2.3.4 in case it helps.

*Duct tape is kinda like the Jedi Force,
It has a dark side, and a light side , and
it holds the world together*

download the script directly

http://www.dma.org/~tw/code/evol2tbird-addressbook/evol2tbird-addressbook.py

I.e., don't cut-n-paste from above. It won't work. Go to that link and then save it.

By the way, this comment editor application is horrid.
-taw

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.