csv2addressbook.pl 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  1. #!/usr/bin/perl -w
  2. use strict;
  3. use Getopt::Long qw(:config pass_through);
  4. use Text::CSV_XS;
  5. # * This file is free software; you can redistribute it and/or modify it
  6. # * under the terms of the GNU General Public License as published by
  7. # * the Free Software Foundation; either version 3 of the License, or
  8. # * (at your option) any later version.
  9. # *
  10. # * This program is distributed in the hope that it will be useful, but
  11. # * WITHOUT ANY WARRANTY; without even the implied warranty of
  12. # * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  13. # * General Public License for more details.
  14. # *
  15. # * You should have received a copy of the GNU General Public License
  16. # * along with this program; if not, write to the Free Software
  17. # * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  18. # *
  19. # * Copyright 2007 Paul Mangan <paul@claws-mail.org>
  20. # *
  21. #
  22. # Import CSV exported address books to Claws Mail
  23. # Supported address books:
  24. # Becky >= 2.41
  25. # Thunderbird >= 2.0.0.6
  26. #
  27. # Becky: full export with titles
  28. # thunderbird: export as 'comma separated'
  29. ###
  30. my $quote_char = '"';
  31. my $esc_char = '"';
  32. my $sep_char = ',';
  33. ###
  34. my $script = "csv2addressbook.pl";
  35. my $type = '';
  36. my $csvfile = '';
  37. my $bookname = '';
  38. my $iNeedHelp = '';
  39. my $known_types = qr/^(?:becky|thunderbird)$/;
  40. GetOptions("type=s" => \$type,
  41. "csv=s" => \$csvfile,
  42. "name=s" => \$bookname,
  43. "help" => \$iNeedHelp);
  44. my @becky_fields = ('Name','E-mail Address', 'Nickname (Input shortcut)',
  45. 'Web Page','Notes','Company','Department','Job Title',
  46. 'Job Role','Last Name','First Name','Middle Name',
  47. 'Birthday','Home Phone','Business Phone','Mobile Phone',
  48. 'Fax','Street','City','State','Postal Code','Country',
  49. 'Delivery Label');
  50. my @tbird_fields = ('First Name','Last Name','Display Name','Nickname',
  51. 'Primary Email','Secondary Email','Work Phone',
  52. 'Home Phone','Fax Number','Pager Number','Mobile Number',
  53. 'Home Address','Home Address 2','Home City','Home State',
  54. 'Home ZipCode','Home Country','Work Address','Work Address 2',
  55. 'Work City','Work State','Work ZipCode','Work Country',
  56. 'Job Title','Department','Organization','Web Page 1',
  57. 'Web Page 2','Birth Year','Birth Month','Birth Day',
  58. 'Custom 1','Custom 2','Custom 3','Custom 4','Notes','junk');
  59. if (grep m/claws-mail/ => `ps -U $ENV{USER}`) {
  60. die("You must quit claws-mail before running this script\n");
  61. }
  62. if ($csvfile eq "" || $type eq "" || $type !~ m/$known_types/ || $iNeedHelp) {
  63. if (!$iNeedHelp) {
  64. if ($csvfile eq "") {
  65. print "ERROR: Option csv is missing!\n";
  66. }
  67. if ($type eq "") {
  68. print "ERROR: Option type is missing!\n";
  69. }
  70. if ($type && $type !~ m/$known_types/) {
  71. print "ERROR: \"$type\" is an unknown type!\n";
  72. }
  73. }
  74. print qq~
  75. Usage:
  76. $script [OPTIONS]
  77. Options:
  78. --help Show this screen
  79. --type=becky|thunderbird Type of exported address book
  80. --csv=FILENAME Full path to CSV file
  81. --name="My new address book" Name of new Claws address book (optional)
  82. ~;
  83. exit;
  84. }
  85. open(INPUT, "<$csvfile") || die("Can't open the CSV file [$csvfile]\n");
  86. my @csvlines = <INPUT>;
  87. close INPUT;
  88. my $config_dir = `claws-mail --config-dir` || die("ERROR:
  89. You don't appear to have Claws Mail installed\n");
  90. chomp $config_dir;
  91. my $claws_version = `claws-mail --version`;
  92. $claws_version =~ s/^Claws Mail version //;
  93. my ($major, $minor) = split(/\./, $claws_version);
  94. my $addr_dir;
  95. if (($major == 3 && $minor >= 1) || $major > 3) {
  96. $addr_dir = "$config_dir/addrbook";
  97. } else {
  98. $addr_dir = $config_dir;
  99. }
  100. my $addr_index = "$addr_dir/addrbook--index.xml";
  101. my $csv = Text::CSV_XS->new({binary => 1,
  102. quote_char => $quote_char,
  103. escape_char => $esc_char,
  104. sep_char => $sep_char});
  105. my $csvtitles = shift(@csvlines);
  106. $csv->parse($csvtitles);
  107. my @csvfields = $csv->fields;
  108. check_fields();
  109. my $new_addrbook = $bookname || get_book_name();
  110. my $xmlobject = write_xml();
  111. chdir;
  112. my @filelist = ();
  113. opendir(ADDR_DIR, $addr_dir) || die("Can't open $addr_dir directory\n");
  114. push(@filelist, (readdir(ADDR_DIR)));
  115. closedir(ADDR_DIR);
  116. my @files = ();
  117. foreach my $file (@filelist) {
  118. if ($file =~ m/^addrbook/ && $file =~ m/[0-9].xml$/) {
  119. push(@files, "$file");
  120. }
  121. }
  122. my @sorted_files = sort {$a cmp $b} @files;
  123. my $latest_file = pop(@sorted_files);
  124. $latest_file =~ s/^addrbook-//;
  125. $latest_file =~ s/.xml$//;
  126. $latest_file++;
  127. my $new_addrbk = "addrbook-"."$latest_file".".xml";
  128. open (NEWADDR, ">$addr_dir/$new_addrbk");
  129. print NEWADDR $xmlobject;
  130. close NEWADDR;
  131. open (ADDRIN, "<$addr_index") || die("can't open $addr_index for reading");
  132. my @addrindex_file = <ADDRIN>;
  133. close ADDRIN;
  134. my $rw_addrindex;
  135. foreach my $addrindex_line (@addrindex_file) {
  136. if ($addrindex_line =~ m/<\/book_list>/) {
  137. $rw_addrindex .= " <book name=\"$new_addrbook\" "
  138. ."file=\"$new_addrbk\" />\n </book_list>\n";
  139. } else {
  140. $rw_addrindex .= "$addrindex_line";
  141. }
  142. }
  143. open (NEWADDRIN, ">$addr_index") || die("Can't open $addr_index for writing");
  144. print NEWADDRIN "$rw_addrindex";
  145. close NEWADDRIN;
  146. print "Done. Address book imported successfully.\n";
  147. exit;
  148. sub get_book_name {
  149. if ($type eq "becky") {
  150. return("Becky address book");
  151. } elsif ($type eq "thunderbird") {
  152. return("Thunderbird address book");
  153. }
  154. }
  155. sub check_fields {
  156. if ($type eq "becky") {
  157. if ($#csvfields != $#becky_fields) {
  158. die("ERROR:\n\tNot enough fields!\n"
  159. ."\tYou need to do a Full Export With Titles\n");
  160. }
  161. } elsif ($type eq "thunderbird") {
  162. if ($#csvfields != $#tbird_fields) {
  163. die("ERROR:\n\tNot enough fields!\n"
  164. ."\tProblem with your exported CSV file\n");
  165. }
  166. }
  167. }
  168. sub write_xml {
  169. my @std_items = get_items();
  170. my @input_fields = get_fields();
  171. my $time = time;
  172. my $xml = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n"
  173. ."<address-book name=\"$new_addrbook\" >\n ";
  174. my $prev_line;
  175. foreach my $line (@csvlines) {
  176. $csv->parse($line);
  177. my @fields = $csv->fields;
  178. # check if an entry contains line breaks
  179. if ($#fields != $#input_fields) {
  180. if ($prev_line) {
  181. my $concat_line = $prev_line.$line;
  182. $csv->parse($concat_line);
  183. @fields = $csv->fields;
  184. if ($#fields != $#input_fields) {
  185. $concat_line =~ s/\r\n$/ /;
  186. $concat_line =~ s/\n$/ /;
  187. $prev_line = $concat_line;
  188. next;
  189. }
  190. } else {
  191. $line =~ s/\r\n$/ /;
  192. $line =~ s/\n$/ /;
  193. $prev_line = $line;
  194. next;
  195. }
  196. }
  197. $prev_line = '';
  198. @fields = escape_fields(@fields);
  199. $xml .= "<person uid=\"$time\" "
  200. ."first-name=\"$fields[$std_items[0]]\" "
  201. ."last-name=\"$fields[$std_items[1]]\" "
  202. ."nick-name=\"$fields[$std_items[2]]\" "
  203. ."cn=\"$fields[$std_items[3]]\">\n ";
  204. $time++;
  205. if ($type eq "thunderbird") {
  206. $xml .= "<address-list>\n "
  207. ."<address uid=\"$time\" alias=\"\" "
  208. ."email=\"$fields[$std_items[4]]\" "
  209. ."remarks=\"\" /> \n";
  210. $time++;
  211. if ($fields[$std_items[5]]) {
  212. $xml .=" <address uid=\"$time\" alias=\"\" "
  213. ."email=\"$fields[$std_items[5]]\" "
  214. ."remarks=\"\" /> \n";
  215. }
  216. $xml .= " </address-list> \n";
  217. } else {
  218. $xml .= "<address-list>\n "
  219. ."<address uid=\"$time\" alias=\"\" "
  220. ."email=\"$fields[$std_items[4]]\" "
  221. ."remarks=\"$fields[$std_items[5]]\" /> \n"
  222. ."</address-list> \n";
  223. }
  224. $xml .= "<attribute-list>\n";
  225. foreach my $item (@std_items) {
  226. delete($fields[$item]);
  227. }
  228. foreach my $field (0 .. $#fields) {
  229. if ($fields[$field]) {
  230. $time++;
  231. $xml .= " <attribute uid=\"$time\" "
  232. ."name=\"$input_fields[$field]\">"
  233. ."$fields[$field]</attribute>\n";
  234. }
  235. }
  236. $xml .= " </attribute-list>\n "
  237. ."</person>\n";
  238. $time++;
  239. }
  240. $xml .= "</address-book>\n";
  241. return $xml;
  242. }
  243. sub get_items {
  244. if ($type eq "becky") {
  245. return ('10','9','2','0','1','4');
  246. } elsif ($type eq "thunderbird") {
  247. return ('0','1','3','2','4','5','38');
  248. }
  249. }
  250. sub get_fields {
  251. if ($type eq "becky") {
  252. return(@becky_fields);
  253. } elsif ($type eq "thunderbird") {
  254. return(@tbird_fields);
  255. }
  256. }
  257. sub escape_fields {
  258. my (@fields) = @_;
  259. for (my $item = 0; $item <= $#fields; $item++) {
  260. $fields[$item] =~ s/^"//;
  261. $fields[$item] =~ s/"$//;
  262. $fields[$item] =~ s/"/&quot;/g;
  263. $fields[$item] =~ s/&/&amp;/g;
  264. $fields[$item] =~ s/'/&apos;/g;
  265. $fields[$item] =~ s/</&lt;/g;
  266. $fields[$item] =~ s/>/&gt;/g;
  267. }
  268. return @fields;
  269. }