update copyright year
[claws.git] / tools / csv2addressbook.pl
1 #!/usr/bin/perl -w
2
3 use strict;
4 use Getopt::Long qw(:config pass_through);
5 use Text::CSV_XS;
6
7 #  * This file is free software; you can redistribute it and/or modify it
8 #  * under the terms of the GNU General Public License as published by
9 #  * the Free Software Foundation; either version 3 of the License, or
10 #  * (at your option) any later version.
11 #  *
12 #  * This program is distributed in the hope that it will be useful, but
13 #  * WITHOUT ANY WARRANTY; without even the implied warranty of
14 #  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15 #  * General Public License for more details.
16 #  *
17 #  * You should have received a copy of the GNU General Public License
18 #  * along with this program; if not, write to the Free Software
19 #  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 #  *
21 #  * Copyright 2007/2008 Paul Mangan <paul@claws-mail.org>
22 #  *
23
24 #
25 # Import CSV exported address books to Claws Mail
26 # Supported address books: 
27 #       Becky >= 2.41
28 #       Thunderbird >= 2.0.0.6
29 #       Kmail >= 1.9.7 / Kaddressbook >= 3.5.7          
30 #               ** kmail bug: can export badly formatted csv **
31 #       Gmail
32 #       Fox Mail
33 #
34
35 # Becky: full export with titles
36 # thunderbird: export as 'comma separated'
37 # kmail/kaddressbook: Export CSV list
38 # gmail: export Outlook format
39 # foxmail: export with all possible headers
40 # basic: a csv file only containing these fields 'First Name', 'Last Name', 'Nickname', 'Email Address'
41
42 ###
43 my $quote_char = '"';
44 my $esc_char = '"';
45 my $sep_char = ',';
46 ###
47
48 my $script = "csv2addressbook.pl";
49 my $type = '';
50 my $csvfile = '';
51 my $bookname = '';
52 my $iNeedHelp = '';
53
54 my $known_types = qr/^(?:becky|thunderbird|kmail|gmail|foxmail|basic)$/;
55
56 GetOptions("type=s" => \$type,
57            "csv=s"  => \$csvfile,
58            "name=s" => \$bookname,
59            "help"   => \$iNeedHelp);
60
61 my @becky_fields = ('Name','E-mail Address', 'Nickname (Input shortcut)',
62                     'Web Page','Notes','Company','Department','Job Title',
63                     'Job Role','Last Name','First Name','Middle Name',
64                     'Birthday','Home Phone','Business Phone','Mobile Phone',
65                     'Fax','Street','City','State','Postal Code','Country',
66                     'Delivery Label');
67 my @tbird_fields = ('First Name','Last Name','Display Name','Nickname',
68                     'Primary Email','Secondary Email','Work Phone',
69                     'Home Phone','Fax Number','Pager Number','Mobile Number',
70                     'Home Address','Home Address 2','Home City','Home State',
71                     'Home ZipCode','Home Country','Work Address','Work Address 2',
72                     'Work City','Work State','Work ZipCode','Work Country',
73                     'Job Title','Department','Organization','Web Page 1',
74                     'Web Page 2','Birth Year','Birth Month','Birth Day',
75                     'Custom 1','Custom 2','Custom 3','Custom 4','Notes',
76                     'Anniversary Year','Anniversary Month','Anniversary Day',
77                     'Category','Spouse name');
78 my @kmail_fields = ('Formatted Name','Family Name','Given Name',
79                     'Additional Names','Honorific Prefixes','Honorific Suffixes',
80                     'Nick Name','Birthday','Home Address Street',
81                     'Home Address City','Home Address Region',
82                     'Home Address Post Code','Home Address Country',
83                     'Home Address Label','Business Address Street',
84                     'Business Address City','Business Address Region',
85                     'Business Address Post Code','Business Address Country',
86                     'Business Address Label','Home Phone','Business Phone',
87                     'Mobile Phone','Home Fax','Business Fax','Car Phone','ISDN',
88                     'Pager','Email Address','Mail Client','Title','Role',
89                     'Organisation','Department','Note','Homepage','Profession',
90                     'Assistant\'s Name','Manager\'s Name','Partner\'s Name',
91                     'Office','IM Address','Anniversary','Blog');
92 my @gmail_fields = ('Name','E-mail Address','Notes','E-mail 2 Address',
93                     'E-mail 3 Address','Mobile Phone','Pager','Company',
94                     'Job Title','Home Phone','Home Phone 2','Home Fax',
95                     'Home Address','Business Phone','Business Phone 2',
96                     'Business Fax','Business Address','Other Phone','Other Fax',
97                     'Other Address','junk');
98 my @foxmail_fields = ('First Name','Last Name','Name','Nickname','e-mail Address',
99                       'Mobile Phone','Pager Number','QQ','ICQ','Personal Home Page',
100                       'Sex','Birthday','Interest','Home Country','Home Province',
101                       'Home City','Home Postal Code','Home Street Address',
102                       'Home Telephone 1','Home Telephone 2','Home Fax','Office Company',
103                       'Office Country','Office Province','Office City',
104                       'Office Postal Code','Office Address','Office HomePage',
105                       'Office Position','Office Department','Office Telephone 1',
106                       'Office Telephone 2','Office Fax','Memo','foxaddrID');
107 my @basic_fields = ('Nickname','e-mail Address');
108
109 if (grep m/claws-mail/ => `ps -U $ENV{USER}`) {
110         die("You must quit claws-mail before running this script\n");
111 }
112
113 if ($csvfile eq "" || $type eq "" || $type !~ m/$known_types/ || $iNeedHelp) {
114         if (!$iNeedHelp) {
115                 if ($csvfile eq "") {
116                         print "ERROR: Option csv is missing!\n";
117                 }
118                 if ($type eq "") {
119                         print "ERROR: Option type is missing!\n";
120                 }
121                 if ($type && $type !~ m/$known_types/) {
122                         print "ERROR: \"$type\" is an unknown type!\n";
123                 }
124         }
125         print qq~
126 Usage:
127         $script [OPTIONS]
128 Options:
129         --help                                  Show this screen
130         --type=becky|thunderbird|kmail|gmail|foxmail|basic
131                                                 Type of exported address book
132         --csv=FILENAME                          Full path to CSV file
133         --name="My new address book"            Name of new Claws address book (optional)
134 ~;
135 exit;
136 }
137
138 open(INPUT, "<$csvfile") || die("Can't open the CSV file [$csvfile]\n");
139         my @csvlines = <INPUT>;
140 close INPUT;
141
142 my $config_dir = `claws-mail --config-dir` || die("ERROR:
143         You don't appear to have Claws Mail installed\n");
144 chomp $config_dir;
145
146 my $claws_version = `claws-mail --version`;
147 $claws_version =~ s/^Claws Mail version //;
148
149 my ($major, $minor) = split(/\./, $claws_version);
150
151 my $addr_dir;
152
153 if (($major == 3 && $minor >= 1) || $major > 3) {
154         $addr_dir = "$config_dir/addrbook";
155 } else {
156         $addr_dir = $config_dir;
157 }
158
159 my $addr_index = "$addr_dir/addrbook--index.xml";
160 my $csv = Text::CSV_XS->new({binary => 1,
161                              quote_char => $quote_char, 
162                              escape_char => $esc_char,
163                              sep_char => $sep_char});
164
165 my $csvtitles = shift(@csvlines);
166
167 $csv->parse($csvtitles);
168 my @csvfields = $csv->fields;
169
170 check_fields();
171
172 my $new_addrbook = $bookname || get_book_name();
173
174 my $xmlobject = write_xml();
175
176 chdir;
177
178 my @filelist = ();
179 opendir(ADDR_DIR, $addr_dir) || die("Can't open $addr_dir directory\n");
180         push(@filelist, (readdir(ADDR_DIR)));
181 closedir(ADDR_DIR);
182
183 my @files = ();
184 foreach my $file (@filelist) {
185         if ($file =~ m/^addrbook/ && $file =~ m/[0-9].xml$/) {
186                 push(@files, "$file");
187         }
188 }
189
190 my @sorted_files = sort {$a cmp $b} @files;
191 my $latest_file = pop(@sorted_files);
192 $latest_file =~ s/^addrbook-//;
193 $latest_file =~ s/.xml$//;
194 $latest_file++;
195 my $new_addrbk = "addrbook-"."$latest_file".".xml";
196
197 open (NEWADDR, ">$addr_dir/$new_addrbk");
198 print NEWADDR $xmlobject;
199 close NEWADDR;
200
201 open (ADDRIN, "<$addr_index") || die("can't open $addr_index for reading");
202         my @addrindex_file = <ADDRIN>;
203 close ADDRIN;
204
205 my $rw_addrindex;
206 foreach my $addrindex_line (@addrindex_file) {
207         if ($addrindex_line =~ m/<\/book_list>/) {
208                 $rw_addrindex .= "    <book name=\"$new_addrbook\" "
209                         ."file=\"$new_addrbk\" />\n  </book_list>\n";
210         } else {
211                 $rw_addrindex .= "$addrindex_line";
212         }
213 }
214
215 open (NEWADDRIN, ">$addr_index") || die("Can't open $addr_index for writing");
216 print NEWADDRIN "$rw_addrindex";
217 close NEWADDRIN;
218
219 print "Done. Address book imported successfully.\n";
220
221 exit;
222
223 sub get_book_name {
224         if ($type eq "becky") {
225                 return("Becky address book");
226         } elsif ($type eq "thunderbird") {
227                 return("Thunderbird address book");
228         } elsif ($type eq "kmail") {
229                 return("Kmail address book");
230         } elsif ($type eq "gmail") {
231                 return("gmail address book");
232         } elsif ($type eq "foxmail") {
233                 return("foxmail address book");
234         } elsif ($type eq "basic") {
235         return("basic address book");
236     }
237 }
238
239 sub check_fields {
240         if ($type eq "becky") {
241                 if ($#csvfields != $#becky_fields) {
242                         die("ERROR:\n\tInvalid field count!\n"
243                            ."\tYou need to do a Full Export With Titles\n");
244                 }
245         } elsif ($type eq "thunderbird") {
246                 if ($#csvfields != $#tbird_fields) {
247                         die("ERROR:\n\tInvalid field count!\n"
248                            ."\tProblem with your exported CSV file\n");
249                 }
250         } elsif ($type eq "kmail") {
251                 if ($#csvfields != $#kmail_fields) {
252                         die("ERROR:\n\tInvalid field count!\n"
253                            ."\tProblem with your exported CSV file\n");
254                 }
255         } elsif ($type eq "gmail") {
256                 if ($#csvfields != $#gmail_fields) {
257                         die("ERROR:\n\tInvalid field count!\n"
258                            ."\tProblem with your exported CSV file\n");
259                 }
260         } elsif ($type eq "foxmail") {
261                 if ($#csvfields != $#foxmail_fields) {
262                         die("ERROR:\n\tInvalid field count!\n"
263                            ."\tProblem with your exported CSV file\n");
264                 }
265         } elsif ($type eq "basic") {
266         if ($#csvfields != $#basic_fields) {
267             die("ERROR:\n\tInvalid field count!\n"
268                     ."\tProblem with your exported CSV file\n");
269         }
270     }
271 }
272
273 sub write_xml {
274         my @std_items = get_items();
275         my @input_fields = get_fields();
276
277         my $time = time;
278         my $xml = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n"
279                  ."<address-book name=\"$new_addrbook\" >\n  ";
280
281         my $prev_line;
282
283         foreach my $line (@csvlines) {
284                 $csv->parse($line);
285                 my @fields = $csv->fields;
286                 # check if an entry contains line breaks
287                 if ($#fields != $#input_fields) {
288                         if ($prev_line) {
289                                 my $concat_line = $prev_line.$line;
290                                 $csv->parse($concat_line);
291                                 @fields = $csv->fields;
292                                 if ($#fields != $#input_fields) {
293                                         $concat_line =~ s/\r\n$/ /;
294                                         $concat_line =~ s/\n$/ /;
295                                         $prev_line = $concat_line;
296                                         next;
297                                 }
298                         } else {
299                                 $line =~ s/\r\n$/ /;
300                                 $line =~ s/\n$/ /;
301                                 $prev_line = $line;
302                                 next;
303                         }
304                 }
305                 $prev_line = '';
306
307                 @fields = escape_fields(@fields);
308
309                 $xml .= "<person uid=\"$time\" "
310                        ."first-name=\"$fields[$std_items[0]]\" "
311                        ."last-name=\"$fields[$std_items[1]]\" "
312                        ."nick-name=\"$fields[$std_items[2]]\" "
313                        ."cn=\"$fields[$std_items[3]]\">\n    ";
314                 $time++;
315                 if ($type eq "thunderbird") {
316                         $xml .= "<address-list>\n      "
317                                ."<address uid=\"$time\" alias=\"\" "
318                                ."email=\"$fields[$std_items[4]]\" "
319                                ."remarks=\"\" />    \n";
320                         $time++;
321                         if ($fields[$std_items[5]]) {
322                                 $xml .="      <address uid=\"$time\" alias=\"\" "
323                                       ."email=\"$fields[$std_items[5]]\" "
324                                       ."remarks=\"\" />    \n";
325                         }
326                         $xml .= "    </address-list>    \n";
327                 } elsif ($type eq "foxmail") {
328                         $xml .= "<address-list>\n      ";
329                         if ($fields[$std_items[4]] =~ m/,/) {
330                                 my @addrs = split(",", $fields[$std_items[4]]);
331                                 my $addr_one = pop(@addrs);
332                                 $xml .= "<address uid=\"$time\" alias=\"\" "
333                                         ."email=\"$addr_one\" "
334                                         ."remarks=\"$fields[$std_items[5]]\" />    \n";
335                                 foreach my $eaddr (@addrs) {
336                                         $time++;
337                                         $xml .= "<address uid=\"$time\" alias=\"\" "
338                                                 ."email=\"$eaddr\" "
339                                                 ."remarks=\"\" />    \n";
340                                 }
341                         } else {
342                                 $xml .= "<address uid=\"$time\" alias=\"\" "
343                                         ."email=\"$fields[$std_items[4]]\" "
344                                         ."remarks=\"$fields[$std_items[5]]\" />    \n";
345                         }
346                         $xml .= "</address-list>    \n";
347                 } else {
348                         $xml .= "<address-list>\n      "
349                                ."<address uid=\"$time\" alias=\"\" "
350                                ."email=\"$fields[$std_items[4]]\" "
351                                ."remarks=\"$fields[$std_items[5]]\" />    \n"
352                                ."</address-list>    \n";
353                 }
354                 $xml .= "<attribute-list>\n";
355                 foreach my $item (@std_items) {
356                         delete($fields[$item]);
357                 }
358                 foreach my $field (0 .. $#fields) {
359                         if ($fields[$field]) { 
360                                 $time++;
361                                 $xml .= "      <attribute uid=\"$time\" "
362                                        ."name=\"$input_fields[$field]\">"
363                                        ."$fields[$field]</attribute>\n";
364                         }
365                 }
366                 $xml .= "    </attribute-list>\n  "
367                        ."</person>\n";
368                 $time++;
369         }
370
371         $xml .= "</address-book>\n";
372
373         return $xml;
374 }
375
376 sub get_items {
377         if ($type eq "becky") {
378                 return ('10','9','2','0','1','4');
379         } elsif ($type eq "thunderbird") {
380                 return ('0','1','3','2','4','5','38');
381         } elsif ($type eq "kmail") {
382                 return ('2','1','6','0','28','34');
383         } elsif ($type eq "gmail") {
384                 return('0','0','0','0','1','2');
385         } elsif ($type eq "foxmail") {
386                 return ('0','1','3','2','4','33');
387         } elsif ($type eq "basic") {
388         return ('0','0','0','0','1','0');
389     }
390 }
391
392 sub get_fields {
393         if ($type eq "becky") {
394                 return(@becky_fields);
395         } elsif ($type eq "thunderbird") {
396                 return(@tbird_fields);
397         } elsif ($type eq "kmail") {
398                 return(@kmail_fields);
399         } elsif ($type eq "gmail") {
400                 return(@gmail_fields);
401         } elsif ($type eq "foxmail") {
402                 return(@foxmail_fields);
403         } elsif ($type eq "basic") {
404         return (@basic_fields);
405     }
406 }
407
408 sub escape_fields {
409         my (@fields) = @_;
410
411         for (my $item = 0; $item <= $#fields; $item++) {
412                 $fields[$item] =~ s/^"//;
413                 $fields[$item] =~ s/"$//;
414                 $fields[$item] =~ s/"/&quot;/g;
415                 $fields[$item] =~ s/&/&amp;/g;
416                 $fields[$item] =~ s/'/&apos;/g;
417                 $fields[$item] =~ s/</&lt;/g;
418                 $fields[$item] =~ s/>/&gt;/g;
419         }
420         
421         return @fields;
422 }
423