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