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