4 use Getopt
::Long
qw(:config pass_through);
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.
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.
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.
21 # * Copyright 2007/2008 Paul Mangan <paul@claws-mail.org>
25 # Import CSV exported address books to Claws Mail
26 # Supported address books:
28 # Thunderbird >= 2.0.0.6
29 # Kmail >= 1.9.7 / Kaddressbook >= 3.5.7
30 # ** kmail bug: can export badly formatted csv **
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'
48 my $script = "csv2addressbook.pl";
54 my $known_types = qr/^(?:becky|thunderbird|kmail|gmail|foxmail|basic)$/;
56 GetOptions
("type=s" => \
$type,
58 "name=s" => \
$bookname,
59 "help" => \
$iNeedHelp);
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',
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');
109 if (grep m/claws-mail/ => `ps -U $ENV{USER}`) {
110 die("You must quit claws-mail before running this script\n");
113 if ($csvfile eq "" || $type eq "" || $type !~ m/$known_types/ || $iNeedHelp) {
115 if ($csvfile eq "") {
116 print "ERROR: Option csv is missing!\n";
119 print "ERROR: Option type is missing!\n";
121 if ($type && $type !~ m/$known_types/) {
122 print "ERROR: \"$type\" is an unknown type!\n";
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
)
138 open(INPUT
, "<$csvfile") || die("Can't open the CSV file [$csvfile]\n");
139 my @csvlines = <INPUT
>;
142 my $config_dir = `claws-mail --config-dir` || die("ERROR:
143 You don't appear to have Claws Mail installed\n");
146 my $claws_version = `claws-mail --version`;
147 $claws_version =~ s/^Claws Mail version //;
149 my ($major, $minor) = split(/\./, $claws_version);
153 if (($major == 3 && $minor >= 1) || $major > 3) {
154 $addr_dir = "$config_dir/addrbook";
156 $addr_dir = $config_dir;
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});
165 my $csvtitles = shift(@csvlines);
167 $csv->parse($csvtitles);
168 my @csvfields = $csv->fields;
172 my $new_addrbook = $bookname || get_book_name
();
174 my $xmlobject = write_xml
();
179 opendir(ADDR_DIR
, $addr_dir) || die("Can't open $addr_dir directory\n");
180 push(@filelist, (readdir(ADDR_DIR
)));
184 foreach my $file (@filelist) {
185 if ($file =~ m/^addrbook/ && $file =~ m/[0-9].xml$/) {
186 push(@files, "$file");
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$//;
195 my $new_addrbk = "addrbook-"."$latest_file".".xml";
197 open (NEWADDR
, ">$addr_dir/$new_addrbk");
198 print NEWADDR
$xmlobject;
201 open (ADDRIN
, "<$addr_index") || die("can't open $addr_index for reading");
202 my @addrindex_file = <ADDRIN
>;
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";
211 $rw_addrindex .= "$addrindex_line";
215 open (NEWADDRIN
, ">$addr_index") || die("Can't open $addr_index for writing");
216 print NEWADDRIN
"$rw_addrindex";
219 print "Done. Address book imported successfully.\n";
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");
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");
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");
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");
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");
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");
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");
274 my @std_items = get_items
();
275 my @input_fields = get_fields
();
278 my $xml = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n"
279 ."<address-book name=\"$new_addrbook\" >\n ";
283 foreach my $line (@csvlines) {
285 my @fields = $csv->fields;
286 # check if an entry contains line breaks
287 if ($#fields != $#input_fields) {
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;
307 @fields = escape_fields
(@fields);
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 ";
315 if ($type eq "thunderbird") {
316 $xml .= "<address-list>\n "
317 ."<address uid=\"$time\" alias=\"\" "
318 ."email=\"$fields[$std_items[4]]\" "
319 ."remarks=\"\" /> \n";
321 if ($fields[$std_items[5]]) {
322 $xml .=" <address uid=\"$time\" alias=\"\" "
323 ."email=\"$fields[$std_items[5]]\" "
324 ."remarks=\"\" /> \n";
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) {
337 $xml .= "<address uid=\"$time\" alias=\"\" "
339 ."remarks=\"\" /> \n";
342 $xml .= "<address uid=\"$time\" alias=\"\" "
343 ."email=\"$fields[$std_items[4]]\" "
344 ."remarks=\"$fields[$std_items[5]]\" /> \n";
346 $xml .= "</address-list> \n";
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";
354 $xml .= "<attribute-list>\n";
355 foreach my $item (@std_items) {
356 delete($fields[$item]);
358 foreach my $field (0 .. $#fields) {
359 if ($fields[$field]) {
361 $xml .= " <attribute uid=\"$time\" "
362 ."name=\"$input_fields[$field]\">"
363 ."$fields[$field]</attribute>\n";
366 $xml .= " </attribute-list>\n "
371 $xml .= "</address-book>\n";
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');
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);
411 for (my $item = 0; $item <= $#fields; $item++) {
412 $fields[$item] =~ s/^"//;
413 $fields[$item] =~ s/"$//;
414 $fields[$item] =~ s/"/"/g;
415 $fields[$item] =~ s/&/&/g;
416 $fields[$item] =~ s/'/'/g;
417 $fields[$item] =~ s/</</g;
418 $fields[$item] =~ s/>/>/g;