Introduction
Like millions of others around the world, I have abandoned Nokia’s smartphones in favour of the slick, open, connected and affordable Android devices – although I still have one of Nokia’s cheapest handsets for backup. The cheapo S40 Nokia has superb battery life and takes 2 SIMs so when the smartphone’s out of juice I can still have home and business lines in action.
Before reading further, I have made a handy Android app that makes all this much easier – you can get it on Google Play here.
However, I did run into some hassle in migrating hundreds of contacts from my Nokia feature phone to the Android world. The Nokia C2-00 allows me to backup to the flash card but, on the face of it, the .nbf backup file produced is a proprietary format. No fear, it’s just a zip file renamed with a weird extension. So, rename and open in your archive tool to find, happily, a whole load of open-format .vcf vCard files, one for each contact in the handset. Great! GMail Contacts allows import in vCard format! Great!
There’s a Perl Script For That…
But wait, I have over 300 contacts to import, do I really have to go through the pointy-clicky GMail Contacts import UI over 300 times? Sounds boring and error prone. So I wrote a script:
#!/usr/bin/perl
################################################################################
#
# Tool to help migrate from a Nokia handset to a Android handset / Google
# contacts.
#
# Marcus Jenkins - January 2012.
#
# Legal disclaimer necessary for those sad people make a living from
# pointless litigation:
# No warranty! If this breaks anything, it isn't my fault.
# Use at your own risk.
#
# You will need the Text::vCard::Addressbook Perl module that you
# can get from www.cpan.org.
#
# In your Nokia S/40 phone, go to Settings / Backup and backup all the contacts
# to a memory card. This will create an NBF file that you can get onto your
# PC, e.g. by plugging a USB cable into the phone. This is how I did it with
# my Nokia C2-00. The NBF file is a zip format file, so you can just rename
# the file so that it has a .zip extension and then extract all the .vcf files
# to a temporary directory. Gmail has a facility at the time of writing to
# import one vCard / vcf file at a time - I don't fancy clicking around for
# ~300 vcf files. So...
#
# Then use this script:
#
# perl convertNokiaBackupToGoogleImport.pl
#
# e.g. perl convertNokiaBackupToGoogleImport.pl contacts contacts.csv
#
# The generated CSV file can then be hand-tweaked if necessary and then
# imported using the GMail web user interface. Then the contacts should
# be automagically available in your Android phones thanks to the brillant
# Google / Android ecosystem.
#
# I used another Perl script to post-filter the CSV file to auto-add country
# code prefrixes, etc., but that logic is fairly well specific to my data set
# so suggest you roll your own or use a spreadsheet app to post-filter prior to
# importing to GMail.
#
# Good luck.
#
################################################################################
use strict;
use Text::vCard::Addressbook;
die if scalar @ARGV != 2;
my $dir = @ARGV[0];
my $outFile = @ARGV[1];
$dir =~ s//$//;
print "Scanning vcf contact files in $dir to $outFile...n";
my $filePattern = "$dir/*vcf";
my @files = glob($filePattern);
my @contacts;
my $maxDefinedPhoneNumbersForAContact;
foreach my $file(@files){
my $adddressBook = Text::vCard::Addressbook->load([$file]);
foreach my $vcard ($adddressBook->vcards()) {
my $rNames = $vcard->get({ 'node_type' => 'N' });
my $nameNode = $rNames->[0];
my $firstName = $nameNode->given();
my $surname = $nameNode->family();
my $rTelephoneNumbers = $vcard->get({ 'node_type' => 'TEL' });
my @phoneNumbersForCSV;
foreach my $telephoneNumber (@$rTelephoneNumbers) {
my $rTypes = $telephoneNumber->types();
my $voiceFlagDefined = 0;
my $specificPhoneTypeDefined = 0;;
foreach my $type (@$rTypes) {
if($type eq 'work') {
push @phoneNumbersForCSV, {type => 'Business',
number => $telephoneNumber->value()};
$specificPhoneTypeDefined = 1;
}
elsif($type eq 'home') {
push @phoneNumbersForCSV, {type => 'Home',
number => $telephoneNumber->value()};
$specificPhoneTypeDefined = 1;
}
elsif($type eq 'mobile' || $type eq 'cell') {
push @phoneNumbersForCSV, {type => 'Mobile',
number => $telephoneNumber->value()};
$specificPhoneTypeDefined = 1;
}
elsif($type eq 'pref' || $type eq '8bit') {
# Ignore
}
elsif($type eq 'voice') {
$voiceFlagDefined = 1;
}
else {
print "Unknown type $typen";
}
}
if($voiceFlagDefined && !$specificPhoneTypeDefined) {
push @phoneNumbersForCSV, {type => 'Home',
number => $telephoneNumber->value()};
}
}
# Update counts for CSV column definition header
if(scalar @phoneNumbersForCSV > $maxDefinedPhoneNumbersForAContact) {
$maxDefinedPhoneNumbersForAContact =
scalar @phoneNumbersForCSV;
}
# Full name needed for sorting and also for Google import
my $fullName = $firstName;
if(length($surname)) {
if(length($fullName)) {
$fullName .= ' ';
}
$fullName .= $surname;
}
my %newContact = (firstName => $firstName, surname => $surname,
fullName => $fullName, phoneNumbers => @phoneNumbersForCSV);
push @contacts, %newContact;
}
}
# Write the CSV file, sorted in first name, last name format where last name
# takes place of the first name if the first name wasn't defined in the Nokia
# handset - just like in the Nokia handset (handy for post-filtering)
open FHOUT, ">$outFile";
print FHOUT 'Name,Given Name,Additional Name,Family Name,Yomi Name,' .
'Given Name Yomi,Additional Name Yomi,Family Name Yomi,Name Prefix,' .
'Name Suffix,Initials,Nickname,Short Name,Maiden Name,Birthday,Gender,' .
'Location,Billing Information,Directory Server,Mileage,Occupation,' .
'Hobby,Sensitivity,Priority,Subject,Notes,Group Membership';
for (my $i = 1; $i <= $maxDefinedPhoneNumbersForAContact; $i ++) {
print FHOUT ",Phone $i - Type,Phone $i - Value";
}
print FHOUT "n";
foreach my $contact (sort sortContactName @contacts) {
print FHOUT
"$contact->{fullName},$contact->{firstName},,$contact->{surname}," .
',,,,,,,,,,,,,,,,,,,,,,';
# print the phone number type / value pairs for this contact
for (my $i = 0; $i < $maxDefinedPhoneNumbersForAContact; $i ++) {
my $rPhoneListing = @{ $contact->{phoneNumbers} }[$i];
my $phoneNumberType;
my $phoneNumber;
if(defined($rPhoneListing)) {
$phoneNumberType = $rPhoneListing->{type};
$phoneNumber = $rPhoneListing->{number};
}
print FHOUT ",$phoneNumberType,$phoneNumber";
}
print FHOUT "n";
}
close FHOUT;
###############################################################################
sub sortContactName {
my $rContact1 = $a;
my $rContact2 = $b;
my $compare1 = $rContact1->{fullName};
my $compare2 = $rContact2->{fullName};
return $compare1 cmp $compare2;
}