aboutsummaryrefslogblamecommitdiffstats
path: root/tools/csv2vcard
blob: b968fbd9c38c42432dbe8f8b2fac2c2c7dad9f5e (plain) (tree)











































































































































































































































                                                                                                                                                                               
#!/usr/bin/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., 59 Temple Place - Suite 330,
# Boston, MA 02111-1307, USA.
#
# Author: Michael MacDonald <mjmac@ximian.com>
#

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 = <IN>) {
  $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 = <IN>;
      $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);