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
47 my $script = "csv2addressbook.pl";
53 my $known_types = qr/^(?:becky|thunderbird|kmail|gmail|foxmail)$/;
55 GetOptions
("type=s" => \
$type,
57 "name=s" => \
$bookname,
58 "help" => \
$iNeedHelp);
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',
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');
107 if (grep m/claws-mail/ => `ps -U $ENV{USER}`) {
108 die("You must quit claws-mail before running this script\n");
111 if ($csvfile eq "" || $type eq "" || $type !~ m/$known_types/ || $iNeedHelp) {
113 if ($csvfile eq "") {
114 print "ERROR: Option csv is missing!\n";
117 print "ERROR: Option type is missing!\n";
119 if ($type && $type !~ m/$known_types/) {
120 print "ERROR: \"$type\" is an unknown type!\n";
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
)
136 open(INPUT
, "<$csvfile") || die("Can't open the CSV file [$csvfile]\n");
137 my @csvlines = <INPUT
>;
140 my $config_dir = `claws-mail --config-dir` || die("ERROR:
141 You don't appear to have Claws Mail installed\n");
144 my $claws_version = `claws-mail --version`;
145 $claws_version =~ s/^Claws Mail version //;
147 my ($major, $minor) = split(/\./, $claws_version);
151 if (($major == 3 && $minor >= 1) || $major > 3) {
152 $addr_dir = "$config_dir/addrbook";
154 $addr_dir = $config_dir;
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});
163 my $csvtitles = shift(@csvlines);
165 $csv->parse($csvtitles);
166 my @csvfields = $csv->fields;
170 my $new_addrbook = $bookname || get_book_name
();
172 my $xmlobject = write_xml
();
177 opendir(ADDR_DIR
, $addr_dir) || die("Can't open $addr_dir directory\n");
178 push(@filelist, (readdir(ADDR_DIR
)));
182 foreach my $file (@filelist) {
183 if ($file =~ m/^addrbook/ && $file =~ m/[0-9].xml$/) {
184 push(@files, "$file");
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$//;
193 my $new_addrbk = "addrbook-"."$latest_file".".xml";
195 open (NEWADDR
, ">$addr_dir/$new_addrbk");
196 print NEWADDR
$xmlobject;
199 open (ADDRIN
, "<$addr_index") || die("can't open $addr_index for reading");
200 my @addrindex_file = <ADDRIN
>;
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";
209 $rw_addrindex .= "$addrindex_line";
213 open (NEWADDRIN
, ">$addr_index") || die("Can't open $addr_index for writing");
214 print NEWADDRIN
"$rw_addrindex";
217 print "Done. Address book imported successfully.\n";
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");
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");
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");
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");
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");
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");
265 my @std_items = get_items
();
266 my @input_fields = get_fields
();
269 my $xml = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n"
270 ."<address-book name=\"$new_addrbook\" >\n ";
274 foreach my $line (@csvlines) {
276 my @fields = $csv->fields;
277 # check if an entry contains line breaks
278 if ($#fields != $#input_fields) {
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;
298 @fields = escape_fields
(@fields);
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 ";
306 if ($type eq "thunderbird") {
307 $xml .= "<address-list>\n "
308 ."<address uid=\"$time\" alias=\"\" "
309 ."email=\"$fields[$std_items[4]]\" "
310 ."remarks=\"\" /> \n";
312 if ($fields[$std_items[5]]) {
313 $xml .=" <address uid=\"$time\" alias=\"\" "
314 ."email=\"$fields[$std_items[5]]\" "
315 ."remarks=\"\" /> \n";
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) {
328 $xml .= "<address uid=\"$time\" alias=\"\" "
330 ."remarks=\"\" /> \n";
333 $xml .= "<address uid=\"$time\" alias=\"\" "
334 ."email=\"$fields[$std_items[4]]\" "
335 ."remarks=\"$fields[$std_items[5]]\" /> \n";
337 $xml .= "</address-list> \n";
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";
345 $xml .= "<attribute-list>\n";
346 foreach my $item (@std_items) {
347 delete($fields[$item]);
349 foreach my $field (0 .. $#fields) {
350 if ($fields[$field]) {
352 $xml .= " <attribute uid=\"$time\" "
353 ."name=\"$input_fields[$field]\">"
354 ."$fields[$field]</attribute>\n";
357 $xml .= " </attribute-list>\n "
362 $xml .= "</address-book>\n";
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');
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);
398 for (my $item = 0; $item <= $#fields; $item++) {
399 $fields[$item] =~ s/^"//;
400 $fields[$item] =~ s/"$//;
401 $fields[$item] =~ s/"/"/g;
402 $fields[$item] =~ s/&/&/g;
403 $fields[$item] =~ s/'/'/g;
404 $fields[$item] =~ s/</</g;
405 $fields[$item] =~ s/>/>/g;