#!@PERL@ -w # # cvs2vcard - Script to convert Outlook CSV files into vCard files # suitable to be imported into Evolution. # # Copyright (C) 2001 Ximian, Inc. # # This program is free software; you can redistribute it and/or # modify it under the terms of version 2 of the GNU General Public # License as published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public # License along with this program; if not, write to the # Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. # # Author: Michael MacDonald # use strict; use diagnostics; use Text::ParseWords; sub usage { print STDERR << "--EndOfUsage"; Takes a CSV-formatted list of contacts from Outlook and attempts to convert it into a list of vCards suitable for import into Evolution. Usage: $0 [infile outfile] --EndOfUsage exit; } sub is_recognized_format { my $line = shift; # Making some assumptions here... Prolly OK. return $line =~ /(First Name|Middle Name|Last Name)/; } sub map_columns { my $line = shift; my @names = parse_line(',', 0, $line); my $ctr = 0; my %fieldmap = map { $_ => $ctr++ } @names; return %fieldmap; } sub build_vcard_attr_from_def { my ($def, $fields, $map) = @_; # Valid chars for lookup (from Outlook CSV) are # A-Za-z0-9_-'/ # Valid chars for formatting of attr are # \s,| my @lookup = map { s/=0A$//; s/[^\w\s\-'\/]//; $_; } split /[\s,]*\|[\s,]*/, $def; foreach my $el (@lookup) { unless (defined($map->{ $el })) { print STDERR "$el is undefined\n"; next; } if (defined($fields->[$map->{ $el }])) { unless ($fields->[$map->{ $el }] =~ /(^$|0\/0\/00)/) { $def =~ s/$el/$fields->[$map->{ $el }]/; } else { $def =~ s/((?<=\|)\s*)?$el(\s*?(?=\|))?(=0A)?,?//; } } else { $def =~ s/((?<=\|)\s*)?$el(\s*?(?=\|))?(=0A)?,?//; } } # Get rid of field delimiters $def =~ s/\|//g; # Snip off any trailing semicolons or whitespace $def =~ s/[\s;]*$//; return $def; } sub build_vcard_from_line { my ($line, %map) = @_; my %vcard; my @fields = parse_line(',', 0, $line); my %vcard_def = ( FN => 'Title |First Name |Middle Name |Last Name |Suffix', N => 'Last Name| Suffix|;First Name|;Middle Name|;Title', 'ADR;WORK' => 'PO Box|;Business Street 2|;Business Street|;Business City|;Business State|;Business Postal Code|;Business Country', 'LABEL;QUOTED-PRINTABLE;WORK' => 'PO Box |Business Street=0A|Business Street 2=0A|Business City,| Business State| Business Postal Code=0A|Business Country', 'TEL;WORK;VOICE' => 'Business Phone', 'TEL;WORK;VOICE2' => 'Business Phone 2', 'TEL;WORK;FAX' => 'Business Fax', 'TEL;WORK;COMPANY' => 'Company Main Phone', 'ADR;HOME' => ';Home Street 2|;Home Street|;Home City|;Home State|;Home Postal Code|;Home Country', 'LABEL;QUOTED-PRINTABLE;HOME' => 'Home Street=0A|Home Street 2=0A|Home City,| Home State| Home Postal Code=0A|Home Country', 'TEL;HOME;VOICE' => 'Home Phone', 'TEL;HOME;VOICE2' => 'Home Phone 2', 'TEL;HOME;FAX' => 'Home Fax', 'ADR;POSTAL' => ';Other Street 2|;Other Street|;Other City|;Other State|;Other Postal Code|;Other Country', 'LABEL;QUOTED-PRINTABLE;POSTAL' => 'Other Street=0A|Other Street 2=0A|Other City,| Other State| Other Postal Code=0A|Other Country', 'TEL;VOICE' => 'Other Phone', 'TEL;FAX' => 'Other Fax', 'TEL;CELL' => 'Mobile Phone', 'TEL;CAR' => 'Car Phone', 'TEL;PAGER' => 'Pager', 'TEL;PREF' => 'Primary Phone', 'TEL;ISDN' => 'ISDN', 'TEL;X-EVOLUTION-CALLBACK' => 'Callback', 'TEL;X-EVOLUTION-TTYTDD' => 'TTY/TDD Phone', 'TEL;X-EVOLUTION-TELEX' => 'Telex', 'TEL;X-EVOLUTION-RADIO' => 'Radio Phone', 'EMAIL;INTERNET' => 'E-mail Address', 'EMAIL;INTERNET2' => 'E-mail 2 Address', 'EMAIL;INTERNET3' => 'E-mail 3 Address', ORG => 'Company|;Department', TITLE => 'Job Title', ROLE => 'Profession', 'X-EVOLUTION-ASSISTANT' => "Assistant's Name", 'TEL;X-EVOLUTION-ASSISTANT' => "Assistant's Phone", 'X-EVOLUTION-SPOUSE' => 'Spouse', 'X-EVOLUTION-ANNIVERSARY' => 'Anniversary', 'X-EVOLUTION-MANAGER' => "Manager's Name", 'X-EVOLUTION-OFFICE' => 'Office Location', BDAY => 'Birthday', NOTE => 'Notes', FBURL => 'Internet Free Busy', URL => 'Web Page', ); foreach my $key (keys(%vcard_def)) { my $attr = build_vcard_attr_from_def($vcard_def{ $key }, \@fields, \%map); if (defined($attr)) { $vcard{ $key } = $attr unless ($attr =~ /^$/); } } return %vcard; } sub print_vcard_to_fh { my ($fh, %vcard) = @_; print $fh "BEGIN:VCARD\n"; foreach my $key (keys(%vcard)) { # Dirty hack because Evolution's vcard stores multiple email addrs # with same sttribute, hence key collision. Bleah. # Ugh! Same deal for multiple phones... (eg. bus. phone) # # And finally, while we're special-casing... Outlook exports dates # differently, so munge 'em if we find 'em. if ($key =~ /EMAIL;INTERNET/o) { (my $temp = $key) =~ s/\d$//; print $fh "$temp:$vcard{ $key }\n"; } elsif ($key =~ /TEL;(HOME|WORK)/o) { (my $temp = $key) =~ s/\d$//; print $fh "$temp:$vcard{ $key }\n"; } elsif ($key =~ /(BDAY|X\-EVOLUTION\-ANNIVERSARY)/o) { my $temp = $vcard{ $key }; if ($temp =~ /(\d\d)\/(\d\d)\/(\d\d)/) { # Y2k !! MS Didn't learn anything. # Hope no one was born before 1915 if ((1900 + $3) < 1915) { print $fh "$key:20$3-$1-$2\n"; } else { print $fh "$key:19$3-$1-$2\n"; } } else { # Something's funky... Just delete the attribute print STDERR "Couldn't figure out what to do with $key:$vcard{ $key }\n"; delete($vcard{ $key }); } } else { print $fh "$key:$vcard{ $key }\n"; } } print $fh "END:VCARD\n\n"; } my $in = $ARGV[0]; my $out = $ARGV[1]; usage() unless(defined($in) && defined($out)); open (IN, $in) or die "Can't open($in): $!\n"; open (OUT, ">$out") or die "Can't open($out): $!\n"; my $linectr = 0; my %map; while (my $line = ) { $line =~ s/\r//g; $line =~ s/\n$//; if ($linectr == 0) { $linectr++; usage() unless is_recognized_format($line); %map = map_columns($line); #if ($line =~ /\r\n$/) { # print STDERR "Apparenlty found DOS-style EOL indicators...\n"; $/ = "\r\n"; #} } else { $linectr++; while ($line =~ /^(("([^"]|\n|"")*")?,)*"([^"]|\n|"")*$/) { my $temp = $line; $line = ; $line =~ s/\r//g; $line =~ s/\n$//; $line = "$temp $line"; } my %vcard = build_vcard_from_line($line, %map); print_vcard_to_fh(\*OUT, %vcard); } } close(IN); close(OUT);