]> git.deb.at Git - pkg/abook.git/blobdiff - filter.c
Merge remote-tracking branch 'upstream/master' into upstream
[pkg/abook.git] / filter.c
index 15370fbf7d606ae64e6043b5a990e9baf4e77d5d..2ea5a2dd77bfa3acafa196f5f324a7b47cee2ed6 100644 (file)
--- a/filter.c
+++ b/filter.c
@@ -2,29 +2,41 @@
 /*
  * $Id$
  *
- * by JH <jheinonen@bigfoot.com>
+ * by JH <jheinonen@users.sourceforge.net>
  *
  * Copyright (C) Jaakko Heinonen
  */
 
+#define _GNU_SOURCE
+
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <ctype.h>
 #include <pwd.h>
+#include <sys/stat.h>
 #include <sys/types.h>
 #include "abook_curses.h"
 #include "filter.h"
 #include "abook.h"
 #include "database.h"
 #include "edit.h"
+#include "gettext.h"
 #include "list.h"
 #include "misc.h"
 #include "options.h"
+#include "ui.h"
+#include "xmalloc.h"
+#include <assert.h>
+
+#ifdef HAVE_VFORMAT
+#include "vcard.h"
+#endif
 
-extern int items;
-extern list_item *database;
-extern struct abook_field abook_fields[];
+extern abook_field_list *fields_list;
+extern int fields_count;
+// see also enum field_types @database.h
+extern abook_field standard_fields[];
 
 /*
  * function declarations
@@ -37,6 +49,10 @@ extern struct abook_field abook_fields[];
 static int      ldif_parse_file(FILE *handle);
 static int     mutt_parse_file(FILE *in);
 static int     pine_parse_file(FILE *in);
+static int     csv_parse_file(FILE *in);
+static int     allcsv_parse_file(FILE *in);
+static int     palmcsv_parse_file(FILE *in);
+static int     vcard_parse_file(FILE *in);
 
 /*
  * export filter prototypes
@@ -46,38 +62,69 @@ static int      ldif_export_database(FILE *out, struct db_enumerator e);
 static int     html_export_database(FILE *out, struct db_enumerator e);
 static int     pine_export_database(FILE *out, struct db_enumerator e);
 static int     csv_export_database(FILE *out, struct db_enumerator e);
-static int     gcrd_export_database(FILE *out, struct db_enumerator e);
+static int     allcsv_export_database(FILE *out, struct db_enumerator e);
+static int     palm_export_database(FILE *out, struct db_enumerator e);
+static int     vcard_export_database(FILE *out, struct db_enumerator e);
 static int     mutt_alias_export(FILE *out, struct db_enumerator e);
+static int     mutt_query_export_database(FILE *out, struct db_enumerator e);
 static int     elm_alias_export(FILE *out, struct db_enumerator e);
 static int     text_export_database(FILE *out, struct db_enumerator e);
 static int     spruce_export_database(FILE *out, struct db_enumerator e);
+static int     wl_export_database(FILE *out, struct db_enumerator e);
+static int     bsdcal_export_database(FILE *out, struct db_enumerator e);
+static int     custom_export_database(FILE *out, struct db_enumerator e);
+
+/*
+ * export filter item prototypes
+ */
+
+void vcard_export_item(FILE *out, int item);
+void muttq_print_item(FILE *file, int item);
+void custom_print_item(FILE *out, int item);
 
 /*
  * end of function declarations
  */
 
 struct abook_input_filter i_filters[] = {
-       { "abook", "abook native format", parse_database },
-       { "ldif", "ldif / Netscape addressbook", ldif_parse_file },
-       { "mutt", "mutt alias (beta)", mutt_parse_file },
-       { "pine", "pine addressbook", pine_parse_file },
+       { "abook", N_("abook native format"), parse_database },
+       { "ldif", N_("ldif / Netscape addressbook"), ldif_parse_file },
+       { "mutt", N_("mutt alias"), mutt_parse_file },
+       { "pine", N_("pine addressbook"), pine_parse_file },
+       { "csv", N_("comma separated values"), csv_parse_file },
+       { "allcsv", N_("comma separated values (all fields)"), allcsv_parse_file },
+       { "palmcsv", N_("Palm comma separated values"), palmcsv_parse_file },
+       { "vcard", N_("vCard file"), vcard_parse_file },
        { "\0", NULL, NULL }
 };
 
 struct abook_output_filter e_filters[] = {
-       { "abook", "abook native format", write_database },
-       { "ldif", "ldif / Netscape addressbook (.4ld)", ldif_export_database },
-       { "mutt", "mutt alias", mutt_alias_export },
-       { "html", "html document", html_export_database },
-       { "pine", "pine addressbook", pine_export_database },
-       { "gcrd", "GnomeCard (VCard) addressbook", gcrd_export_database },
-       { "csv", "comma separated values", csv_export_database },
-       { "elm", "elm alias", elm_alias_export },
-       { "text", "plain text", text_export_database },
-       { "spruce", "Spruce address book", spruce_export_database },
+       { "abook", N_("abook native format"), write_database },
+       { "ldif", N_("ldif / Netscape addressbook (.4ld)"), ldif_export_database },
+       { "vcard", N_("vCard 2 file"), vcard_export_database },
+       { "mutt", N_("mutt alias"), mutt_alias_export },
+       { "muttq", N_("mutt query format (internal use)"), mutt_query_export_database },
+       { "html", N_("html document"), html_export_database },
+       { "pine", N_("pine addressbook"), pine_export_database },
+       { "csv", N_("comma separated values"), csv_export_database },
+       { "allcsv", N_("comma separated values (all fields)"), allcsv_export_database },
+       { "palmcsv", N_("Palm comma separated values"), palm_export_database},
+       { "elm", N_("elm alias"), elm_alias_export },
+       { "text", N_("plain text"), text_export_database },
+       { "wl", N_("Wanderlust address book"), wl_export_database },
+       { "spruce", N_("Spruce address book"), spruce_export_database },
+       { "bsdcal", N_("BSD calendar"), bsdcal_export_database },
+       { "custom", N_("Custom format"), custom_export_database },
        { "\0", NULL, NULL }
 };
 
+struct abook_output_item_filter u_filters[] = {
+       { "vcard", N_("vCard 2 file"), vcard_export_item },
+       { "muttq", N_("mutt alias"), muttq_print_item },
+       { "custom", N_("Custom format"), custom_print_item },
+       { "\0", NULL }
+};
+
 /*
  * common functions
  */
@@ -86,18 +133,25 @@ void
 print_filters()
 {
        int i;
-       
-       puts("input:");
+
+       puts(_("input formats:"));
        for(i=0; *i_filters[i].filtname ; i++)
                printf("\t%s\t%s\n", i_filters[i].filtname,
-                       i_filters[i].desc);
+                       gettext(i_filters[i].desc));
 
        putchar('\n');
-       
-       puts("output:");
+
+       puts(_("output formats:"));
        for(i=0; *e_filters[i].filtname ; i++)
                printf("\t%s\t%s\n", e_filters[i].filtname,
-                       e_filters[i].desc);
+                       gettext(e_filters[i].desc));
+
+       putchar('\n');
+
+       puts(_("query-compatible output formats:"));
+       for(i=0; *u_filters[i].filtname ; i++)
+               printf("\t%s\t%s\n", u_filters[i].filtname,
+                       gettext(u_filters[i].desc));
 
        putchar('\n');
 }
@@ -134,13 +188,13 @@ get_real_name()
 
        pwent = getpwnam(username);
 
-       if( (tmp = malloc(strlen(pwent->pw_gecos) +1)) == NULL)
-               return strdup(username);
+       if((tmp = xstrdup(pwent->pw_gecos)) == NULL)
+               return xstrdup(username);
 
        rtn = sscanf(pwent->pw_gecos, "%[^,]", tmp);
        if (rtn == EOF || rtn == 0) {
                free(tmp);
-               return strdup(username);
+               return xstrdup(username);
        } else
                return tmp;
 }
@@ -148,28 +202,28 @@ get_real_name()
 /*
  * import
  */
-       
+
 static int             i_read_file(char *filename, int (*func) (FILE *in));
 
 static void
 import_screen()
 {
        int i;
-       
+
        clear();
 
        refresh_statusline();
-       headerline("import database");
+       headerline(_("import database"));
+
+       mvaddstr(3, 1, _("please select a filter"));
 
-       mvaddstr(3, 1, "please select a filter");
-       
 
        for(i=0; *i_filters[i].filtname ; i++)
                mvprintw(5 + i, 6, "%c -\t%s\t%s\n", 'a' + i,
                        i_filters[i].filtname,
-                       i_filters[i].desc);
+                       gettext(i_filters[i].desc));
 
-       mvprintw(6 + i, 6, "x -\tcancel");
+       mvprintw(6 + i, 6, _("x -\tcancel"));
 }
 
 int
@@ -177,31 +231,30 @@ import_database()
 {
        int filter;
        char *filename;
-       int tmp = items;
+       int tmp = db_n_items();
 
        import_screen();
-       
+
        filter = getch() - 'a';
        if(filter == 'x' - 'a' ||
                filter >= number_of_input_filters() || filter < 0) {
                refresh_screen();
                return 1;
        }
-       
+
        mvaddstr(5+filter, 2, "->");
-       
-       filename = ask_filename("Filename: ", 1);
-       if( !filename ) {
+
+       filename = ask_filename(_("Filename: "));
+       if(!filename) {
                refresh_screen();
                return 2;
        }
-               
-       if(  i_read_file(filename, i_filters[filter].func ) )
-               statusline_msg("Error occured while opening the file");
-       else
-       if( tmp == items )
-               statusline_msg("Hmm.., file seems not to be a valid file");
-       
+
+       if(i_read_file(filename, i_filters[filter].func ))
+               statusline_msg(_("Error occured while opening the file"));
+       else if(tmp == db_n_items())
+               statusline_msg(_("File does not seem to be a valid addressbook"));
+
        refresh_screen();
        free(filename);
 
@@ -216,43 +269,60 @@ i_read_file(char *filename, int (*func) (FILE *in))
        FILE *in;
        int ret = 0;
 
-       if( ( in = fopen( filename, "r" ) ) == NULL )
+       if( (in = abook_fopen( filename, "r" )) == NULL )
                return 1;
 
        ret = (*func) (in);
 
        fclose(in);
 
-       return ret;     
+       return ret;
 }
 
 int
-import(char filtname[FILTNAME_LEN], char *filename)
+import_file(char filtname[FILTNAME_LEN], char *filename)
 {
        int i;
-       int tmp = items;
+       int tmp = db_n_items();
        int ret = 0;
 
        for(i=0;; i++) {
-               if( ! strncmp(i_filters[i].filtname, filtname, FILTNAME_LEN) )
+               if(! strncasecmp(i_filters[i].filtname, filtname,
+                                       FILTNAME_LEN) )
                        break;
-               if( ! *i_filters[i].filtname ) {
+               if(! *i_filters[i].filtname) {
                        i = -1;
                        break;
                }
        }
 
-       if( i<0 )
+       if(i < 0)
                return -1;
 
-       if( !strcmp(filename, "-") )
-               ret = (*i_filters[i].func) (stdin);
+#ifdef HAVE_VFORMAT
+       // this is a special case for
+       // libvformat whose API expects a filename
+       if(!strcmp(filtname, "vcard")) {
+         if(!strcmp(filename, "-"))
+           ret = vcard_parse_file_libvformat("/dev/stdin");
+         else
+           ret = vcard_parse_file_libvformat(filename);
+       }
        else
+#endif
+
+       if(!strcmp(filename, "-")) {
+               struct stat s;
+               if((fstat(fileno(stdin), &s)) == -1 || S_ISDIR(s.st_mode))
+                       ret = 1;
+               else
+                       ret = (*i_filters[i].func) (stdin);
+       } else
                ret =  i_read_file(filename, i_filters[i].func);
-       
-       if( tmp == items )
+
+       if(tmp == db_n_items())
                ret = 1;
-       
+
        return ret;
 }
 
@@ -260,29 +330,29 @@ import(char filtname[FILTNAME_LEN], char *filename)
  * export
  */
 
-static int             e_write_file(char *filename,
+static int e_write_file(char *filename,
                int (*func) (FILE *in, struct db_enumerator e), int mode);
 
 static void
 export_screen()
 {
        int i;
-       
+
        clear();
 
 
        refresh_statusline();
-       headerline("export database");
+       headerline(_("export database"));
 
-       mvaddstr(3, 1, "please select a filter");
-       
+       mvaddstr(3, 1, _("please select a filter"));
 
-       for(i=0; *e_filters[i].filtname ; i++)
+
+       for(i = 0; *e_filters[i].filtname ; i++)
                mvprintw(5 + i, 6, "%c -\t%s\t%s\n", 'a' + i,
                        e_filters[i].filtname,
-                       e_filters[i].desc);
+                       gettext(e_filters[i].desc));
 
-       mvprintw(6 + i, 6, "x -\tcancel");
+       mvprintw(6 + i, 6, _("x -\tcancel"));
 }
 
 int
@@ -293,44 +363,67 @@ export_database()
        char *filename;
 
        export_screen();
-       
+
        filter = getch() - 'a';
        if(filter == 'x' - 'a' ||
-               filter >= number_of_output_filters(e_filters) || filter < 0) {
+               filter >= number_of_output_filters() || filter < 0) {
                refresh_screen();
                return 1;
        }
-       
-       mvaddstr(5+filter, 2, "->");
 
-       if( selected_items() ) {
-               statusline_addstr("Export All/Selected/Cancel (A/s/c)");
-               switch( tolower(getch()) ) {
-                       case 's':
+       mvaddstr(5 + filter, 2, "->");
+
+       if(selected_items()) {
+               switch(statusline_askchoice(
+                       _("Export <a>ll, export <s>elected, or <c>ancel?"),
+                       S_("keybindings:all/selected/cancel|asc"), 3)) {
+                       case 1:
+                               break;
+                       case 2:
                                enum_mode = ENUM_SELECTED;
                                break;
-                       case 'c':
-                               clear_statusline();
+                       case 0:
+                       case 3:
+                               refresh_screen();
                                return 1;
                }
                clear_statusline();
        }
-       
-       filename = ask_filename("Filename: ", 0);
-       if( !filename ) {
+
+       filename = ask_filename(_("Filename: "));
+       if(!filename) {
                refresh_screen();
                return 2;
        }
-       
-       if(  e_write_file(filename, e_filters[filter].func, enum_mode ) )
-               statusline_msg("Error occured while exporting");
-       
+
+       if( e_write_file(filename, e_filters[filter].func, enum_mode))
+               statusline_msg(_("Error occured while exporting"));
+
        refresh_screen();
        free(filename);
 
        return 0;
 }
 
+struct abook_output_item_filter select_output_item_filter(char filtname[FILTNAME_LEN]) {
+       int i;
+       for(i=0;; i++) {
+               if(!strncasecmp(u_filters[i].filtname, filtname, FILTNAME_LEN))
+                 break;
+               if(!*u_filters[i].filtname) {
+                 i = -1;
+                 break;
+               }
+       }
+       return u_filters[i];
+}
+
+void
+e_write_item(FILE *out, int item, void (*func) (FILE *in, int item))
+{
+  (*func) (out, item);
+}
+
 static int
 e_write_file(char *filename, int (*func) (FILE *in, struct db_enumerator e),
                int mode)
@@ -339,16 +432,16 @@ e_write_file(char *filename, int (*func) (FILE *in, struct db_enumerator e),
        int ret = 0;
        struct db_enumerator enumerator = init_db_enumerator(mode);
 
-       if( (out = fopen(filename, "a")) == NULL )
+       if((out = fopen(filename, "a")) == NULL)
                return 1;
 
-       if( ftell(out) )
+       if(ftell(out))
                return 1;
 
        ret = (*func) (out, enumerator);
-       
+
        fclose(out);
-       
+
        return ret;
 }
 
@@ -359,9 +452,10 @@ fexport(char filtname[FILTNAME_LEN], FILE *handle, int enum_mode)
        struct db_enumerator e = init_db_enumerator(enum_mode);
 
        for(i=0;; i++) {
-               if( ! strncmp(e_filters[i].filtname, filtname, FILTNAME_LEN) )
+               if(!strncasecmp(e_filters[i].filtname, filtname,
+                                       FILTNAME_LEN))
                        break;
-               if( ! *e_filters[i].filtname ) {
+               if(!*e_filters[i].filtname) {
                        i = -1;
                        break;
                }
@@ -370,29 +464,30 @@ fexport(char filtname[FILTNAME_LEN], FILE *handle, int enum_mode)
        return (e_filters[i].func) (handle, e);
 }
 
-       
+
 
 int
-export(char filtname[FILTNAME_LEN], char *filename)
+export_file(char filtname[FILTNAME_LEN], char *filename)
 {
        const int mode = ENUM_ALL;
        int i;
        int ret = 0;
        struct db_enumerator e = init_db_enumerator(mode);
-       
+
        for(i=0;; i++) {
-               if( ! strncmp(e_filters[i].filtname, filtname, FILTNAME_LEN) )
+               if(!strncasecmp(e_filters[i].filtname, filtname,
+                                       FILTNAME_LEN))
                        break;
-               if( ! *e_filters[i].filtname ) {
+               if(!*e_filters[i].filtname) {
                        i = -1;
                        break;
                }
        }
 
-       if( i<0 )
+       if(i < 0)
                return -1;
 
-       if( !strcmp(filename, "-") )
+       if(!strcmp(filename, "-"))
                ret = (e_filters[i].func) (stdout, e);
        else
                ret =  e_write_file(filename, e_filters[i].func, mode);
@@ -410,145 +505,259 @@ export(char filtname[FILTNAME_LEN], char *filename)
 
 #include "ldif.h"
 
-static void    ldif_fix_string(char *str);
-
-#ifndef LINESIZE
-#      define LINESIZE 1024
-#endif
-
-#define        LDIF_ITEM_FIELDS        15
-
-typedef char*  ldif_item[LDIF_ITEM_FIELDS];
-
+/* During LDIF import we need more fields than the
+   ITEM_FIELDS of a *list_item. Eg: "objectclass"
+   to test valid records, ...
+   Here we extends the existing field_types enum
+   to define new fields indexes usable during processing.
+   Newly created LDIF attr names could be associated to
+   them using ldif_conv_table[]. */
+typedef enum {
+       LDIF_OBJECTCLASS = ITEM_FIELDS + 1
+} ldif_field_types;
+
+#define        LDIF_ITEM_FIELDS        LDIF_OBJECTCLASS
+
+typedef char *ldif_item[LDIF_ITEM_FIELDS];
+
+/* LDIF field's names *must* respect the ordering
+   defined by the field_types enum from database.h
+   This is only used to define (for export only)
+   abook standard field to LDIF attr name mappings */
 static ldif_item ldif_field_names = {
-       "cn",   
-       "mail",
-       "streetaddress",
-        "locality",
-       "st",
-       "postalcode",
-       "countryname",
-       "homephone",
-       "description",
-       "homeurl",
-       "facsimiletelephonenumber",
-       "cellphone",
-       "xmozillaanyphone",
-       "xmozillanickname",
-       "objectclass", /* this must be the last entry */
+       "cn",                   // NAME
+       "mail",                 // EMAIL
+       "streetaddress",        // ADDRESS
+       "streetaddress2",       // ADDRESS2
+       "locality",             // CITY
+       "st",                   // STATE
+       "postalcode",           // ZIP
+       "countryname",          // COUNTRY
+       "homephone",            // PHONE
+       "telephonenumber",      // WORKPHONE
+       "facsimiletelephonenumber",     // FAX
+       "cellphone",            // MOBILEPHONE
+       "xmozillanickname",     // NICK
+       "homeurl",              // URL
+       "description",          // NOTES
+       "anniversary",          // ANNIVERSARY
+       "ou",                   // GROUPS
 };
 
-static int ldif_conv_table[LDIF_ITEM_FIELDS] = {
-       NAME,           /* "cn" */
-       EMAIL,          /* "mail" */
-       ADDRESS,        /* "streetaddress" */
-        CITY,          /* "locality" */
-       STATE,          /* "st" */
-       ZIP,            /* "postalcode" */
-       COUNTRY,        /* "countryname" */
-       PHONE,          /* "homephone" */
-       NOTES,          /* "description" */
-       URL,            /* "homeurl" */
-       FAX,            /* "facsimiletelephonenumber" */
-       MOBILEPHONE,    /* "cellphone" */
-       WORKPHONE,      /* "xmozillaanyphone" */
-       NICK,           /* "xmozillanickname" */
-       -1,             /* "objectclass" */ /* this must be the last entry */
+/* Used to map LDIF attr names from input to
+   the abook restricted set of standard fields. */
+typedef struct {
+       char *key;
+       int  index;
+} ldif_available_items;
+
+static ldif_available_items ldif_conv_table[] = {
+       /* initial field names respect the field_types enum
+          from database.h but this is only for readability.
+          This ldif_item struct allow use to define multiple
+          LDIF field names ("attribute names") for one abook field */
+
+       {"cn",                  NAME},          // 0
+       {"mail",                EMAIL},
+       {"streetaddress",       ADDRESS},
+       {"streetaddress2",      ADDRESS2},
+       {"locality",            CITY},
+       {"st",                  STATE},
+       {"postalcode",          ZIP},
+       {"countryname",         COUNTRY},
+       {"homephone",           PHONE},
+       {"telephonenumber",     WORKPHONE},     // workphone, according to Mozilla
+       {"facsimiletelephonenumber",    FAX},
+       {"cellphone",           MOBILEPHONE},
+       {"mozillanickname",     NICK},
+       {"homeurl",             URL},
+       {"description",         NOTES},
+       {"anniversary",         ANNIVERSARY},
+       {"ou",                  GROUPS},        // 16
+
+       // here comes a couple of aliases
+       {"mozillasecondemail",  EMAIL},
+       {"homecity",            CITY},
+       {"zip",                 ZIP},
+       {"tel",                 PHONE},
+       {"xmozillaanyphone",    WORKPHONE},     // ever used ?
+       {"workphone",           WORKPHONE},
+       {"fax",                 FAX},
+       {"telexnumber",         FAX},
+       {"mobilephone",         MOBILEPHONE},
+       {"mobile",              MOBILEPHONE},
+       {"xmozillanickname",    NICK},
+       {"labeledURI",          URL},
+       {"notes",               NOTES},
+       {"birthday",            ANNIVERSARY},
+       {"category",            GROUPS},
+
+       /* TODO:
+          "sn": append to lastname ?
+          "surname": append to lastname ?
+          "givenname": prepend to firstname ? */
+
+       /* here starts dummy fields:
+
+          As long as additional indexes are created
+          (using the above ldif_field_types),
+          any other LDIF attr name can be added and
+          used during ldif entry processing.
+          But obviously fields > ITEM_FIELDS (database.h) won't be
+          copied into the final *list_item. */
+
+       /* - to avoid mistake, don't use the special ITEM_FIELDS value.
+          - see also: http://mxr.mozilla.org/comm-central/source/mailnews/addrbook/src/nsAbLDIFService.cpp */
+
+       // used to check valid LDIF records:
+       {"objectclass",         LDIF_OBJECTCLASS}
 };
+const int LDIF_IMPORTABLE_ITEM_FIELDS = (int)sizeof(ldif_conv_table)/sizeof(*ldif_conv_table);
 
-
-static char * 
-ldif_read_line(FILE *in)
+/*
+  Handles multi-line strings.
+  If a string starts with a space, it's the continuation
+  of the previous line. Thus we need to always read ahead.
+  But for this to work with stdin, we need to stores the next
+  line for later use in case it's not a continuation of the
+  first line.
+ */
+static char *
+ldif_read_line(FILE *in, char **next_line)
 {
-       char line[LINESIZE];
-       char *buf=NULL;
+       char *buf = NULL;
        char *ptr, *tmp;
-       long pos;
-       int i;
+       char *line;
+
+       // buf filled with the first line
+       if(!*next_line)
+               buf = getaline(in);
+       else {
+               buf = xstrdup(*next_line);
+               xfree(*next_line);
+       }
 
-       for(i=1;;i++) {
-               pos = ftell(in);
-               fgets(line, LINESIZE, in);
-               
-               if( feof(in) )
-                       break;
-               
-               if(i == 1) {
-                       buf = strdup(line);
-                       continue;
-               }
-               
+       while(!feof(in)) {
+               // if no line already read-ahead.
+               line = getaline(in);
+               if(!line) break;
+
+               // this is not a continuation of what is already in buf
+               // store it for the next round
                if(*line != ' ') {
-                       fseek(in, pos, SEEK_SET);
+                       *next_line = line;
                        break;
                }
 
-               ptr = (char *)&line;
+               // starts with ' ': this is the continuation of buf
+               ptr = line;
                while( *ptr == ' ')
                        ptr++;
 
                tmp = buf;
                buf = strconcat(buf, ptr, NULL);
                free(tmp);
+               free(line);
        }
 
-       if( *buf == '#' ) {
+       if(buf && *buf == '#' ) {
                free(buf);
                return NULL;
        }
-               
-       if(buf) {
-               int i,j;
-               for(i=0,j=0; j < strlen(buf); i++, j++)
-                       buf[i] = buf[j] == '\n' ? buf[++j] : buf[j];
-       }
 
        return buf;
 }
 
 static void
-ldif_add_item(ldif_item ldif_item)
+ldif_add_item(ldif_item li)
 {
-       list_item abook_item;
+       list_item item;
        int i;
 
-       memset(abook_item, 0, sizeof(abook_item));
-       
-       if( !ldif_item[LDIF_ITEM_FIELDS -1] )
+       /* if there's no value for "objectclass"
+          it's probably a buggy record */
+       if(!li[LDIF_OBJECTCLASS])
                goto bail_out;
-       
 
-       for(i=0; i < LDIF_ITEM_FIELDS; i++) {
-               if(ldif_conv_table[i] >= 0 && ldif_item[i] && *ldif_item[i] )
-                       abook_item[ldif_conv_table[i]] = strdup(ldif_item[i]);
+       /* just copy from our extended ldif_item to a regular
+          list_item,
+          TODO: API could be changed so db_fput_byid() is usable */
+       item = item_create();
+       for(i=0; i < ITEM_FIELDS; i++) {
+               if(li[i] && *li[i])
+                       item[i] = xstrdup(li[i]);
        }
 
-       add_item2database(abook_item);
+       add_item2database(item);
+       item_free(&item);
 
 bail_out:
        for(i=0; i < LDIF_ITEM_FIELDS; i++)
-               my_free(ldif_item[i]);
-
+               xfree(li[i]);
 }
 
 static void
 ldif_convert(ldif_item item, char *type, char *value)
 {
-       int i;
-
-       if( !strcmp(type, "dn") ) {
+       /* this is the first (mandatory) attribute to expected
+          from a new valid LDIF record.
+          The previous record must be added to the database before
+          we can go further with the new one */
+       if(!strcmp(type, "dn")) {
                ldif_add_item(item);
                return;
        }
 
-       for(i=0; i < LDIF_ITEM_FIELDS; i++) {
-               if( !safe_strcmp(ldif_field_names[i], type) && *value ) {
-                       if( i == LDIF_ITEM_FIELDS -1) /* this is a dirty hack */
-                               if( safe_strcmp("person", value))
-                                       break;
-                       if(item[i])
-                               my_free(item[i]);
-                       item[i] = strdup(value);
+       int i, index;
+
+       for( i=0; i < LDIF_IMPORTABLE_ITEM_FIELDS; i++ ) {
+
+               if( *value &&                                           // there's a value for the attr provided
+                   ldif_conv_table[i].key &&                           // there exists an ldif attr name...
+                   !strcasecmp(ldif_conv_table[i].key, type)) {        // ...matching that provided at input
+
+                       assert((i >= 0) && (i < LDIF_ITEM_FIELDS));
+                       // which abook field this attribute's name maps to ?
+                       index = ldif_conv_table[i].index;
+                       assert((index >= 0) && (index < LDIF_ITEM_FIELDS));
+
+                       /* TODO: here must be handled multi-valued cases
+                          (first or latest win, append or prepend values, ...)
+                          Currently: emails are appended, for other fields the
+                          first attribute found wins.
+                          Eg: the value of "mobile" will be taken into
+                          account if such a line comes before "cellphone". */
+
+                       /* Remember: LDIF_ITEM_FIELDS > ITEM_FIELDS,
+                          lower (common) indexes of *ldif_item map well to *list_item.
+                          We can use item_fput()... */
+                       if(index < ITEM_FIELDS) {
+                               // multiple email support, but two only will stay
+                               // in the final *list_item
+                               if(index == EMAIL && item_fget(item, EMAIL)) {
+                                       item_fput(item,
+                                                 EMAIL,
+                                                 strconcat(item_fget(item, EMAIL), ",", value, 0));
+                               }
+                               else {
+                                       /* Don't override already initialized fields:
+                                          This is the rule of the "first win" */
+                                       if(! item_fget(item, index))
+                                               item_fput(item, index, xstrdup(value));
+                               }
+                       }
+
+                       /* ... but if the ldif field's name index is higher
+                          than what standards abook fields struct can hold,
+                          these extra indexes must be managed manually.
+                          This is the case of LDIF_OBJECTCLASS ("objectclass" attr) */
+                       else {
+                               item[index] = xstrdup(value);
+                       }
+
+                       // matching attr found and field filled:
+                       // no further attr search is needed for `type`
+                       break;
                }
        }
 }
@@ -557,46 +766,40 @@ static int
 ldif_parse_file(FILE *handle)
 {
        char *line = NULL;
+       char *next_line = NULL;
        char *type, *value;
        int vlen;
+
+       /* This is our extended fields holder to put the values from
+          successfully parsed LDIF attributes.
+          ldif_item item is temporary. When the end of an entry is reached,
+          values are copied into a regular *list_item struct, see ldif_add_item() */
        ldif_item item;
 
        memset(item, 0, sizeof(item));
 
        do {
-               if( ! (line = ldif_read_line(handle)) )
-                       continue;
+               line = ldif_read_line(handle, &next_line);
 
-               if( -1 == ( str_parse_line(line, &type, &value, &vlen)) ) {
-                       my_free(line);
+               // EOF or empty lines: continue;
+               if(!line || *line == '\0') continue;
+
+               if(-1 == (str_parse_line(line, &type, &value, &vlen))) {
+                       xfree(line);
                        continue; /* just skip the errors */
                }
-                               
-               ldif_fix_string(value);
 
                ldif_convert(item, type, value);
 
-               my_free(line);
+               xfree(line);
        } while ( !feof(handle) );
 
+       // force registration (= ldif_add_item()) of the last LDIF entry
        ldif_convert(item, "dn", "");
 
        return 0;
 }
 
-static void
-ldif_fix_string(char *str)
-{
-       int i, j;
-
-       for( i = 0, j = 0; j < strlen(str); i++, j++)
-               str[i] = ( str[j] == (char)0xc3 ?
-                               (char) str[++j] + (char) 0x40 :
-                               str[j] );
-
-       str[i] = 0;
-}
-
 /*
  * end of ldif import
  */
@@ -605,134 +808,152 @@ ldif_fix_string(char *str)
  * mutt alias import filter
  */
 
-enum {
-       MUTT_ALIAS,
-       MUTT_NAME,
-       MUTT_EMAIL
-};
-
-static void
-remove_newlines(char *buf)
-{
-       int i,j;
-       
-       for(i=0,j=0; j < strlen(buf); i++, j++)
-               buf[i] = buf[j] == '\n' ? buf[++j] : buf[j];
-}
+#include "getname.h"
 
 static int
-mutt_read_line(FILE *in, char **alias, char **rest)
+mutt_read_line(FILE *in, char **groups, char **alias, char **rest)
 {
-       char line[LINESIZE];
-       char *ptr=(char *)&line;
-       char *tmp;
+       char *line, *ptr;
+       char *start, *end;
+       abook_list *glist = NULL;
 
-       fgets(line, LINESIZE, in);
-       remove_newlines(line);
+       if( !(line = ptr = getaline(in)) )
+               return 1; /* error / EOF */
 
-       while( ISSPACE(*ptr) )
-               ptr++;
+       SKIPWS(ptr);
 
-       if( strncmp("alias", ptr, 5) ) {
+       if(strncmp("alias", ptr, 5)) {
+               free(line);
                return 1;
        }
-               
-       ptr += 5;
-
-       while( ISSPACE(*ptr) )
-               ptr++;
 
-       tmp = ptr;
+       ptr += 5;
+       SKIPWS(ptr);
+
+       /* If the group option is used, save the groups */
+       *groups = NULL;
+       start = ptr;
+       int n_groups;
+       for(n_groups = 0; 0 == strncmp("-group", ptr, 6); n_groups++) {
+               ptr += 6;
+               SKIPWS(ptr);
+               start = ptr;
+               SKIPNONWS(ptr);
+               end = ptr;
+               abook_list_append(&glist,xstrndup(start, end - start));
+               SKIPWS(ptr);
+       }
 
-       while( ! ISSPACE(*ptr) )
-               ptr++;
+       if(n_groups && groups)
+               *groups = abook_list_to_csv(glist);
 
-       if( (*alias = malloc(ptr-tmp+1)) == NULL)
-               return 1;
+       abook_list_free(&glist);        
 
-       strncpy(*alias, tmp, ptr-tmp);
-       *(*alias+(ptr-tmp)) = 0;
+       /* alias */
+       start = ptr;
+       SKIPNONWS(ptr);
+       end = ptr;
+       SKIPWS(ptr);
+       if(alias)
+               *alias = xstrndup(start, end - start);
 
-       while( ISSPACE(*ptr) )
-               ptr++;
+       /* rest (email) */
+       *rest = xstrdup(ptr);
 
-       *rest = strdup(ptr);    
-       
+       xfree(line);
        return 0;
 }
 
 static void
-mutt_parse_email(char *mutt_item[3])
+mutt_fix_quoting(char *p)
 {
-       char *tmp;
-       int i;
-
-       if( (tmp = strchr(mutt_item[MUTT_NAME], '<')) )
-               *tmp = 0;
-       else
-               return;
+       char *escape = 0;
 
-       mutt_item[MUTT_EMAIL] = strdup(tmp+1);
-
-       if( (tmp = strchr(mutt_item[MUTT_EMAIL], '>')) )
-               *tmp = 0;
-
-       tmp = mutt_item[MUTT_NAME];
-
-       for(i=strlen(tmp)-1; i>0; i--)
-               if(ISSPACE(tmp[i]))
-                       tmp[i] = 0;
-               else
-                       break;
-
-       mutt_item[MUTT_NAME] = strdup(tmp);
-
-       free(tmp);
+       for(; *p; p++) {
+               switch(*p) {
+                       case '\"':
+                               if(escape)
+                                       *escape = ' ';
+                               break;
+                       case '\\':
+                               escape = p;
+                               break;
+                       default:
+                               escape = 0;
+               }
+       }
 }
 
 static void
-mutt_add_mutt_item(char *mutt_item[3])
+mutt_parse_email(list_item item)
 {
-       list_item abook_item;
+       char *line = item_fget(item, NAME);
+       char *tmp;
+       char *name, *email;
+#if 0
+       char *start = line;
+       int i = 0;
+#endif
+
+       mutt_fix_quoting(line);
+       tmp = strconcat("From: ", line, NULL);
+       getname(tmp, &name, &email);
+       free(tmp);
 
-       memset(abook_item, 0, sizeof(abook_item));
+       if(name)
+               item_fput(item, NAME, name);
+       else
+               return;
 
-       abook_item[NAME] = safe_strdup(mutt_item[MUTT_NAME]);
-       abook_item[EMAIL] = safe_strdup(mutt_item[MUTT_EMAIL]);
-       abook_item[NICK] = safe_strdup(mutt_item[MUTT_ALIAS]);
+       if(email)
+               item_fput(item, EMAIL, email);
+       else
+               return;
 
-       add_item2database(abook_item);
+       /*
+        * this is completely broken
+        */
+#if 0
+       while( (start = strchr(start, ',')) && i++ < MAX_EMAILS - 1) {
+               tmp = strconcat("From: ", ++start, NULL);
+               getname(tmp, &name, &email);
+               free(tmp);
+               free(name);
+               if(email) {
+                       if(*email) {
+                               tmp = strconcat(item[EMAIL], ",", email, NULL);
+                               free(item[EMAIL]);
+                               item[EMAIL] = tmp;
+                       } else {
+                               xfree(email);
+                       }
+               }
+       }
+#endif
 }
 
 static int
 mutt_parse_file(FILE *in)
 {
-       char *mutt_item[3];
+       list_item item = item_create();
 
        for(;;) {
+               memset(item, 0, fields_count * sizeof(char *));
 
-               memset(mutt_item, 0, sizeof(mutt_item) );
-               
-               if( mutt_read_line(in, &mutt_item[MUTT_ALIAS],
-                               &mutt_item[MUTT_NAME]) )
-                       continue;
-
-               mutt_parse_email(mutt_item);
+               if(!mutt_read_line(in,
+                       (field_id(GROUPS) != -1) ? &item[field_id(GROUPS)] : NULL,
+                       (field_id(NICK) != -1) ? &item[field_id(NICK)] : NULL,
+                       &item[field_id(NAME)]) )
+                       mutt_parse_email(item);
 
-               if( feof(in) ) {
-                       free(mutt_item[MUTT_ALIAS]);
-                       free(mutt_item[MUTT_NAME]);
-                       free(mutt_item[MUTT_EMAIL]);
+               if(feof(in)) {
+                       item_empty(item);
                        break;
                }
 
-               mutt_add_mutt_item(mutt_item);
-
-               free(mutt_item[MUTT_ALIAS]);
-               free(mutt_item[MUTT_NAME]);
-               free(mutt_item[MUTT_EMAIL]);
+               add_item2database(item);
        }
-
+       item_free(&item);
 
        return 0;
 }
@@ -762,6 +983,7 @@ static int
 ldif_export_database(FILE *out, struct db_enumerator e)
 {
        char email[MAX_EMAILSTR_LEN];
+       abook_list *emails, *em;
 
        fprintf(out, "version: 1\n");
 
@@ -770,19 +992,32 @@ ldif_export_database(FILE *out, struct db_enumerator e)
                int j;
                get_first_email(email, e.item);
 
-               tmp = mkstr("cn=%s,mail=%s", database[e.item][NAME], email);
+               if(*email)
+                       tmp = strdup_printf("cn=%s,mail=%s",db_name_get(e.item),email);
+               /* TODO: this may not be enough for a trully "Distinguished" name
+                  needed by LDAP. Appending a random uuid could do the trick */
+               else
+                       tmp = strdup_printf("cn=%s",db_name_get(e.item));
+
                ldif_fput_type_and_value(out, "dn", tmp);
                free(tmp);
 
-               for(j=0; j < LDIF_ITEM_FIELDS; j++) {
-                       if(ldif_conv_table[j] >= 0) {
-                               if(ldif_conv_table[j] == EMAIL)
-                                       ldif_fput_type_and_value(out,
-                                               ldif_field_names[j], email);
-                               else if(database[e.item][ldif_conv_table[j]])
-                                       ldif_fput_type_and_value(out,
-                                               ldif_field_names[j],
-                                               database[e.item][ldif_conv_table[j]]);
+               for(j = 0; j < ITEM_FIELDS; j++) {
+                       if(j == EMAIL) {
+                               if(*email) {
+                                       tmp = db_email_get(e.item);
+                                       emails = csv_to_abook_list(tmp);
+                                       free(tmp);
+                                       for(em = emails; em; em = em->next)
+                                               ldif_fput_type_and_value(out,
+                                                                        ldif_field_names[EMAIL],
+                                                                        em->data);
+                               }
+                       }
+                       else if(db_fget(e.item,j)) {
+                               ldif_fput_type_and_value(out,
+                                                        ldif_field_names[j],
+                                                        db_fget(e.item, j));
                        }
                }
 
@@ -801,33 +1036,59 @@ ldif_export_database(FILE *out, struct db_enumerator e)
  * html export filter
  */
 
-static void            html_export_write_head(FILE *out, int extra_column);
+static void            html_export_write_head(FILE *out);
 static void            html_export_write_tail(FILE *out);
 
+extern struct index_elem *index_elements;
+
+static void
+html_print_emails(FILE *out, struct list_field *f)
+{
+       abook_list *l = csv_to_abook_list(f->data);
+
+       for(; l; l = l->next) {
+               fprintf(out, "<a href=\"mailto:%s\">%s</a>", l->data, l->data);
+               if(l->next)
+                       fprintf(out, ", ");
+       }
+
+       abook_list_free(&l);
+}
+
 static int
 html_export_database(FILE *out, struct db_enumerator e)
 {
-       char tmp[MAX_EMAILSTR_LEN];
-       int extra_column = options_get_int("extra_column");
+       struct list_field f;
+       struct index_elem *cur;
 
-       if( items < 1 )
+       if(list_is_empty())
                return 2;
 
-       extra_column = (extra_column > 2 && extra_column < ITEM_FIELDS) ?
-               extra_column : PHONE;
+       init_index();
 
-       html_export_write_head(out, extra_column);
+       html_export_write_head(out);
 
        db_enumerate_items(e) {
-               get_first_email(tmp, e.item);
-               fprintf(out, "<tr>\n<td><a href=\"mailto:%s\">%s</a>\n",
-                               tmp,
-                               database[e.item][NAME] );
-                               
-               fprintf(out, "<td>%s\n<td>%s\n",
-                               database[e.item][EMAIL],
-                               safe_str(database[e.item][extra_column]) );
-               fprintf(out, "</tr>\n\n");
+               fprintf(out, " <tr>\n");
+               for(cur = index_elements; cur; cur = cur->next) {
+                       if(cur->type != INDEX_FIELD)
+                               continue;
+                       
+                       get_list_field(e.item, cur, &f);
+
+                       fprintf(out, "  <td>");
+
+                       if(f.type == FIELD_EMAILS) {
+                               html_print_emails(out, &f);
+                       } else {
+                               if (strcmp(safe_str(f.data), "") == 0)
+                                       fprintf(out, "&nbsp;");
+                               else
+                                       fprintf(out, "%s", safe_str(f.data));
+                       }
+                       fprintf(out, "</td>\n");
+               }
+               fprintf(out, " </tr>\n");
        }
 
        html_export_write_tail(out);
@@ -835,22 +1096,51 @@ html_export_database(FILE *out, struct db_enumerator e)
        return 0;
 }
 
-
 static void
-html_export_write_head(FILE *out, int extra_column)
+html_export_write_head(FILE *out)
 {
-       char *realname = get_real_name();
+       char *realname = get_real_name(), *str;
+       struct index_elem *cur;
+
+       fprintf(out, "<!DOCTYPE html>\n");
+       fprintf(out, "<html>\n");
+       fprintf(out, "<head>\n");
+       fprintf(out, " <meta charset=\"utf-8\" />\n");
+       fprintf(out, " <title>");
+       fprintf(out, _("%s's addressbook"), realname );
+       fprintf(out, "</title>\n");
+       fprintf(out, " <style type=\"text/css\">\n");
+       fprintf(out, "  table {border-collapse: collapse ; border: 1px solid #000;}\n");
+       fprintf(out, "  table th, table td {text-align: left; border: 1px solid #000; padding: 0.33em;}\n");
+       fprintf(out, "  table th {border-bottom: 3px double #000; background-color: #ccc;}\n");
+       fprintf(out, " </style>\n");
+       fprintf(out, "</head>\n");
+       fprintf(out, "<body>\n");
+       fprintf(out, "<h1>");
+       fprintf(out, _("%s's addressbook"), realname);
+       fprintf(out, "</h1>\n");
+
+       fprintf(out, "<table>\n");
+       fprintf(out, "<thead>\n");
+       fprintf(out, " <tr>\n");
+       for(cur = index_elements; cur; cur = cur->next) {
+               if(cur->type != INDEX_FIELD)
+                       continue;
+
+               get_field_info(cur->d.field.id, NULL, &str, NULL);
 
-       fprintf(out, "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\">\n");
-       fprintf(out, "<html>\n<head>\n  <title>%s's addressbook</title>",
-                       realname );
-       fprintf(out, "\n</head>\n<body>\n");
-       fprintf(out, "\n<h2>%s's addressbook</h2>\n", realname );
-       fprintf(out, "<br><br>\n\n");
+               fprintf(out, "  <th>");
+
+               if (strcmp(str, "") == 0)
+                       fprintf(out, "&nbsp;");
+               else
+                       fprintf(out, "%s", str);
 
-       fprintf(out, "<center><table border>\n");
-       fprintf(out, "\n<tr><th>Name<th>E-mail address(es)<th>%s</tr>\n\n",
-                       abook_fields[extra_column].name);
+               fprintf(out, "</th>\n");
+       }
+       fprintf(out, " </tr>\n");
+       fprintf(out, "<thead>\n");
+       fprintf(out, "<tbody>\n");
 
        free(realname);
 }
@@ -858,10 +1148,11 @@ html_export_write_head(FILE *out, int extra_column)
 static void
 html_export_write_tail(FILE *out)
 {
-       fprintf(out, "\n</table></center>\n");
-       fprintf(out, "\n</body>\n</html>\n");
+       fprintf(out, "</table>\n");
+       fprintf(out, "</body>\n");
+       fprintf(out, "</html>");
 }
-       
+
 /*
  * end of html export filter
  */
@@ -871,12 +1162,14 @@ html_export_write_tail(FILE *out)
  * pine addressbook import filter
  */
 
+#define PINE_BUF_SIZE 2048
+
 static void
 pine_fixbuf(char *buf)
 {
        int i,j;
 
-       for(i=0,j=0; j < strlen(buf); i++, j++)
+       for(i = 0,j = 0; j < (int)strlen(buf); i++, j++)
                buf[i] = buf[j] == '\n' ? buf[++j] : buf[j];
 }
 
@@ -886,19 +1179,19 @@ pine_convert_emails(char *s)
        int i;
        char *tmp;
 
-       if( s == NULL || *s != '(' )
+       if(s == NULL || *s != '(')
                return;
 
-       for(i=0; s[i]; i++ )
-               s[i] = s[i+1];
+       for(i = 0; s[i]; i++)
+               s[i] = s[i + 1];
 
        if( ( tmp = strchr(s,')')) )
-               *tmp=0;
-       
-       for(i=1; ( tmp = strchr(s, ',') ) != NULL ; i++, s=tmp+1 )
-               if( i > 3 ) {
-                       *tmp = 0;
-                       break;  
+               *tmp = '\0';
+
+       for(i = 1; ( tmp = strchr(s, ',') ) != NULL ; i++, s = tmp + 1)
+               if(i > MAX_LIST_ITEMS - 1) {
+                       *tmp = '\0';
+                       break;
                }
 
 }
@@ -909,32 +1202,35 @@ pine_parse_buf(char *buf)
        list_item item;
        char *start = buf;
        char *end;
-       char tmp[400];
+       char tmp[PINE_BUF_SIZE];
        int i, len, last;
        int pine_conv_table[]= {NICK, NAME, EMAIL, -1, NOTES};
 
-       memset(&item, 0, sizeof(item) );
-       
+       item = item_create();
+
        for(i=0, last=0; !last ; i++) {
-               if( ! (end = strchr(start, '\t')) )
+               if( !(end = strchr(start, '\t')) )
                        last=1;
-               
+
                len = last ? strlen(start) : (int) (end-start);
-               len = min(len, 400-1);
-       
-               if(i < sizeof(pine_conv_table) / sizeof(*pine_conv_table)
+               len = min(len, PINE_BUF_SIZE - 1);
+
+               if(i < (int)(sizeof(pine_conv_table) / sizeof(*pine_conv_table))
                                && pine_conv_table[i] >= 0) {
                        strncpy(tmp, start, len);
                        tmp[len] = 0;
-                       item[pine_conv_table[i]] = strdup(tmp);
+                       if(*tmp)
+                               item_fput(item, pine_conv_table[i],
+                                               xstrdup(tmp));
                }
                start = end + 1;
        }
-       
-       pine_convert_emails(item[EMAIL]);
+
+       pine_convert_emails(item_fget(item, EMAIL));
        add_item2database(item);
+       item_free(&item);
 }
-               
+
 
 #define LINESIZE       1024
 
@@ -942,33 +1238,36 @@ static int
 pine_parse_file(FILE *in)
 {
        char line[LINESIZE];
-       char *buf=NULL;
+       char *buf = NULL;
        char *ptr;
        int i;
 
-       fgets(line, LINESIZE, in);      
-       
+       fgets(line, LINESIZE, in);
+
        while(!feof(in)) {
-               for(i=2;;i++) {
-                       buf = realloc(buf, i*LINESIZE);
-                       if(i==2)
+               for(i = 2;;i++) {
+                       buf = xrealloc(buf, i*LINESIZE);
+                       if(i == 2)
                                strcpy(buf, line);
                        fgets(line, LINESIZE, in);
                        ptr=(char *)&line;
-                       if(*ptr != ' ' || feof(in) )
+                       if(*ptr != ' ' || feof(in))
                                break;
                        else
-                               while( *ptr == ' ') ptr++;
-                               
+                               while(*ptr == ' ')
+                                       ptr++;
+
                        strcat(buf, ptr);
                }
-               if( *buf == '#' )
+               if(*buf == '#') {
+                       xfree(buf);
                        continue;
+               }
                pine_fixbuf(buf);
 
                pine_parse_buf(buf);
 
-               my_free(buf);
+               xfree(buf);
        }
 
        return 0;
@@ -989,14 +1288,18 @@ pine_parse_file(FILE *in)
 static int
 pine_export_database(FILE *out, struct db_enumerator e)
 {
+       char *emails;
+
        db_enumerate_items(e) {
-               fprintf(out, have_multiple_emails(e.item) ?
+               emails = db_email_get(e.item);
+               fprintf(out, strchr(emails, ',') /* multiple addresses? */ ?
                                "%s\t%s\t(%s)\t\t%s\n" : "%s\t%s\t%s\t\t%s\n",
-                               safe_str(database[e.item][NICK]),
-                               safe_str(database[e.item][NAME]),
-                               safe_str(database[e.item][EMAIL]),
-                               safe_str(database[e.item][NOTES])
+                               safe_str(db_fget(e.item, NICK)),
+                               safe_str(db_name_get(e.item)),
+                               emails,
+                               safe_str(db_fget(e.item, NOTES))
                                );
+               free(emails);
        }
 
        return 0;
@@ -1008,155 +1311,991 @@ pine_export_database(FILE *out, struct db_enumerator e)
 
 
 /*
- * csv addressbook export filter
+ * csv import filter
  */
 
-static int
-csv_export_database(FILE *out, struct db_enumerator e)
-{
-       int j;
-       int csv_export_fields[] = {
-               NAME,
-               EMAIL,
-               PHONE,
-               NOTES,
-               -1
-       };
-
-       db_enumerate_items(e) {
-               for(j = 0; csv_export_fields[j] >= 0; j++) {
-                       fprintf(out, strchr(safe_str(database[e.item][csv_export_fields[j]]), ',') ?
-                               "\"%s\"" : "%s",
-                               safe_str(database[e.item][csv_export_fields[j]])
-                               );
-                       if(csv_export_fields[j+1] >= 0)
-                               fputc(',', out);
-               }
-               fputc('\n', out);
-       }
-               
+/* This is used by both allcsv_export_database() and csv_export_common()
+   to distinguish between standard and defined fields.
+   To avoid confusions this should stay > ITEM_FIELDS  */
+#define CUSTOM_FIELD_START_INDEX       (ITEM_FIELDS + 10)
 
+/* FIXME
+ * these files should be parsed according to a certain
+ * lay out, or the default if layout is not given, at
+ * the moment only default is done...
+ */
 
-       return 0;
-}
+#define CSV_COMMENT_CHAR       '#'
+#define CSV_DUPLICATE_SEPARATOR        " "
+#define CSV_TABLE_SIZE(t)      (sizeof (t) / sizeof *(t))
 
-/*
- * end of csv export filter
- */
+static int csv_conv_table[] = {
+       NAME,
+       EMAIL,
+       PHONE,
+       NOTES,
+       NICK
+};
 
+static int allcsv_conv_table[] = {
+       NAME,
+       EMAIL,
+       ADDRESS,
+       ADDRESS2,
+       CITY,
+       STATE,
+       ZIP,
+       COUNTRY,
+       PHONE,
+       WORKPHONE,
+       FAX,
+       MOBILEPHONE,
+       NICK,
+       URL,
+       NOTES,
+       ANNIVERSARY
+};
 
-/*
- * GnomeCard (VCard) addressbook export filter
- */
+static int palmcsv_conv_table[] = {
+       NAME,           /* Last name */
+       NAME,           /* First name */
+       NOTES,          /* Title */
+       NICK,           /* Company */
+       WORKPHONE,
+       PHONE,
+       FAX,
+       MOBILEPHONE,
+       EMAIL,
+       ADDRESS,
+       CITY,
+       STATE,
+       ZIP,
+       COUNTRY,
+       ANNIVERSARY,
+};
 
-static int
-gcrd_export_database(FILE *out, struct db_enumerator e)
+static void
+csv_convert_emails(char *s)
 {
-       char emails[MAX_EMAILS][MAX_EMAIL_LEN];
-       int j;
-       char *name;
-
-       db_enumerate_items(e) {
-               fprintf(out, "BEGIN:VCARD\nFN:%s\n",
-                               safe_str(database[e.item][NAME]));
-
-               name = get_surname(database[e.item][NAME]);
-               for( j = strlen(database[e.item][NAME]) - 1; j >= 0; j-- ) {
-                       if(database[e.item][NAME][j] == ' ')
-                               break;
-               } 
-               fprintf(out, "N:%s;%.*s\n",
-                       safe_str(name),
-                       j,
-                       safe_str(database[e.item][NAME])
-                       ); 
+       int i;
+       char *tmp;
 
-               free(name);
+       if(s == NULL)
+               return;
 
-               if ( database[e.item][ADDRESS] )
-                       fprintf(out, "ADR:;;%s;%s;%s;%s;%s\n",
-                               safe_str(database[e.item][ADDRESS]),
-                               safe_str(database[e.item][CITY]),
-                               safe_str(database[e.item][STATE]),
-                               safe_str(database[e.item][ZIP]),
-                               safe_str(database[e.item][COUNTRY])
-                               );
-               
-               if (database[e.item][PHONE])
-                       fprintf(out, "TEL;HOME:%s\n", database[e.item][PHONE]);
-               if (database[e.item][WORKPHONE])
-                       fprintf(out, "TEL;WORK:%s\n", database[e.item][WORKPHONE]);
-               if (database[e.item][FAX])
-                       fprintf(out, "TEL;FAX:%s\n", database[e.item][FAX]);
-               if (database[e.item][MOBILEPHONE])
-                       fprintf(out, "TEL;CELL:%s\n", database[e.item][MOBILEPHONE]);
-
-               if ( database[e.item][EMAIL] ) {
-                       split_emailstr(e.item, emails);
-                       for(j=0; j < MAX_EMAILS ; j++) {
-                               if ( *emails[j] ) 
-                                       fprintf(out, "EMAIL;INTERNET:%s\n",
-                                               emails[j]);
-                       }
+       for(i = 1; ( tmp = strchr(s, ',') ) != NULL ; i++, s = tmp + 1)
+               if(i > MAX_LIST_ITEMS - 1) {
+                       *tmp = 0;
+                       break;
                }
-               
-               if ( database[e.item][NOTES] ) 
-                       fprintf(out, "NOTE:%s\n", database[e.item][NOTES]);
-               if (database[e.item][URL])
-                       fprintf(out, "URL:%s\n",  database[e.item][URL]);
 
-               fprintf(out, "END:VCARD\n\n");
-               
+}
+
+static char *
+csv_remove_quotes(char *s)
+{
+       char *copy, *trimmed;
+       int len;
+
+       copy = trimmed = xstrdup(s);
+       strtrim(trimmed);
+
+       len = strlen(trimmed);
+       if(trimmed[len - 1] == '\"' && *trimmed == '\"') {
+               if(len < 3) {
+                       xfree(copy);
+                       return NULL;
+               }
+               trimmed[len - 1] = 0;
+               trimmed++;
+               trimmed = xstrdup(trimmed);
+               free(copy);
+               return trimmed;
        }
 
-       return 0;
+       xfree(copy);
+       return xstrdup(s);
 }
 
-/*
- * end of GnomeCard export filter
- */
-
+static int
+csv_field_to_item(int *table_base, size_t table_size, int field)
+{
+       if(field < table_size)
+               return field_id(table_base[field]);
 
-/*
- * mutt alias export filter
- */
+       return -1;
+}
 
-static char *
-mutt_alias_genalias(int i)
+static void
+csv_store_item(list_item item, int i, char *s)
 {
-       char *tmp, *pos;
-       
-       if(database[i][NICK])
-               return strdup(database[i][NICK]);
+       char *newstr = NULL;
 
-       tmp = strdup(database[i][NAME]);
+       if(!s || !*s)
+               return;
 
-       if( ( pos = strchr(tmp, ' ') ) )
-               *pos = 0;
+       if( !(newstr = csv_remove_quotes(s)) )
+               return;
 
-       strlower(tmp);
+       if(i >= 0) {
+               if (item[i] != NULL) {
+                       char *oldstr = item[i];
 
-       return tmp;     
+                       item[i] = strconcat(newstr, CSV_DUPLICATE_SEPARATOR,
+                               oldstr, NULL);
+                       xfree(newstr);
+                       xfree(oldstr);
+               } else {
+                       item[i] = newstr;
+               }
+       } else {
+               xfree(newstr);
+       }
 }
 
 static int
-mutt_alias_export(FILE *out, struct db_enumerator e)
+csv_is_valid_quote_end(char *p)
 {
-       char email[MAX_EMAIL_LEN];
-       char *alias = NULL;
+       if(*p != '\"')
+               return FALSE;
+
+       for(p++; *p; p++) {
+               if(*p == ',')
+                       return TRUE;
+               else if(!ISSPACE(*p))
+                       return FALSE;
+       }
 
-       db_enumerate_items(e) {
-               alias = mutt_alias_genalias(e.item);
+       return TRUE;
+}
 
-               get_first_email(email, e.item);
-               fprintf(out, *email ? "alias %s %s <%s>\n": "alias %s %s%s\n",
-                               alias,
-                               database[e.item][NAME],
-                               email);
-               my_free(alias);
+static int
+csv_is_valid_quote_start(char *p)
+{
+       for(; *p; p++) {
+               if(*p == '\"')
+                       return TRUE;
+               else if(!ISSPACE(*p))
+                       return FALSE;
        }
 
-       return 0;
+       return FALSE;
+}
+
+static void
+csv_parse_line(char *line, int *table_base, size_t table_size)
+{
+       char *p, *start;
+       int field;
+       bool in_quote = FALSE;
+       list_item item;
+
+       item = item_create();
+
+       for(p = start = line, field = 0; *p; p++) {
+               if(in_quote) {
+                       if(csv_is_valid_quote_end(p))
+                               in_quote = FALSE;
+               } else {
+                       if ( (((p - start) / sizeof (char)) < 2 ) &&
+                               csv_is_valid_quote_start(p) )
+                               in_quote = TRUE;
+               }
+
+               if(*p == ',' && !in_quote) {
+                       *p = 0;
+                       csv_store_item(item,
+                               csv_field_to_item(table_base,table_size,field),
+                               start);
+                       field++;
+                       start = p + 1;
+               }
+       }
+       /*
+        * store last field
+        */
+       csv_store_item(item, csv_field_to_item(table_base, table_size, field),
+               start);
+
+       csv_convert_emails(item_fget(item, EMAIL));
+       add_item2database(item);
+       item_free(&item);
+}
+
+static int
+csv_parse_file_common(FILE *in, int *conv_table, size_t table_size)
+{
+       char *line = NULL;
+
+       while(!feof(in)) {
+               line = getaline(in);
+
+               if(line && *line && *line != CSV_COMMENT_CHAR)
+                       csv_parse_line(line, conv_table, table_size);
+
+               xfree(line);
+       }
+
+       return 0;
+}
+
+static int
+csv_parse_file(FILE *in)
+{
+       return csv_parse_file_common(in, csv_conv_table,
+               CSV_TABLE_SIZE(csv_conv_table));
+}
+
+static int
+allcsv_parse_file(FILE *in)
+{
+       return csv_parse_file_common(in, allcsv_conv_table,
+               CSV_TABLE_SIZE(allcsv_conv_table));
+}
+
+static int
+palmcsv_parse_file(FILE *in)
+{
+       return csv_parse_file_common(in, palmcsv_conv_table,
+               CSV_TABLE_SIZE(palmcsv_conv_table));
+}
+
+/*
+ * end of csv import filter
+ */
+
+/*
+ * vCard import filter
+ */
+
+static char *vcard_fields[] = {
+       "FN",                   /* FORMATTED NAME */
+       "EMAIL",                /* EMAIL */
+       "ADR",                  /* ADDRESS */
+       "ADR",                  /* ADDRESS2 */
+       "ADR",                  /* CITY */
+       "ADR",                  /* STATE */
+       "ADR",                  /* ZIP */
+       "ADR",                  /* COUNTRY */
+       "TEL",                  /* PHONE */
+       "TEL",                  /* WORKPHONE */
+       "TEL",                  /* FAX */
+       "TEL",                  /* MOBILEPHONE */
+       "NICKNAME",             /* NICK */
+       "URL",                  /* URL */
+       "NOTE",                 /* NOTES */
+       "BDAY",                 /* ANNIVERSARY */
+       "N",                    /* NAME: special case/mapping in vcard_parse_line() */
+       NULL                    /* ITEM_FIELDS */
+};
+
+enum {
+       VCARD_KEY = 0,
+       VCARD_KEY_ATTRIBUTE,
+       VCARD_VALUE,
+};
+
+static char *
+vcard_get_line_element(char *line, int element)
+{
+       int i;
+       char *line_copy = 0;
+       char *result = 0;
+       char *key = 0;
+       char *key_attr = 0;
+       char *value = 0;
+
+       line_copy = xstrdup(line);
+
+       /* change newline characters, if present, to end of string */
+       for(i=0; line_copy[i]; i++) {
+               if(line_copy[i] == '\r' || line_copy[i] == '\n') {
+                       line_copy[i] = '\0';
+                       break;
+               }
+       }
+
+       /* separate key from value */
+       for(i=0; line_copy[i]; i++) {
+               if(line_copy[i] == ':') {
+                       line_copy[i] = '\0';
+                       key = line_copy;
+                       value = &line_copy[i+1];
+                       break;
+               }
+       }
+
+       /* separate key from key attributes */
+       /* works for vCard 2 as well (automagically) */
+       if (key) {
+               for(i=0; key[i]; i++) {
+                       if(key[i] == ';') {
+                               key[i] = '\0';
+                               key_attr = &key[i+1];
+                               break;
+                       }
+               }
+       }
+
+       switch(element) {
+       case VCARD_KEY:
+               if(key)
+                       result = xstrdup(key);
+               break;
+       case VCARD_KEY_ATTRIBUTE:
+               if(key_attr)
+                       result = xstrdup(key_attr);
+               break;
+       case VCARD_VALUE:
+               if(value)
+                       result = xstrdup(value);
+               break;
+       }
+
+       xfree(line_copy);
+       return result;
+}
+
+static void
+vcard_parse_email(list_item item, char *line)
+{
+       char *email;
+
+       email = vcard_get_line_element(line, VCARD_VALUE);
+
+       if(item[1]) {
+               item[1] = strconcat(item[1], ",", email, 0);
+               xfree(email);
+       }
+       else {
+               item[1] = email;
+       }
+}
+
+
+/*
+ * mappings between vCard ADR field and abook's ADDRESS
+ * see rfc2426 section 3.2.1
+ */
+static void
+vcard_parse_address(list_item item, char *line)
+{
+       char *value;
+
+       value = vcard_get_line_element(line, VCARD_VALUE);
+       if(!value)
+               return;
+
+       // vCard(post office box) - not used
+       strsep(&value, ";");
+       if(!value) return;
+
+       // vCard(the extended address)
+       item_fput(item, ADDRESS2, xstrdup(strsep(&value, ";")));
+       if(!value) return;
+
+       // vCard(the street address)
+       item_fput(item, ADDRESS, xstrdup(strsep(&value, ";")));
+       if(!value) return;
+
+       // vCard(the locality)
+       item_fput(item, CITY, xstrdup(strsep(&value, ";")));
+       if(!value) return;
+
+       // vCard(the region)
+       item_fput(item, STATE, xstrdup(strsep(&value, ";")));
+       if(!value) return;
+
+       // vCard(the postal code)
+       item_fput(item, ZIP, xstrdup(strsep(&value, ";")));
+       if(!value) return;
+
+       // vCard(the country name)
+       item_fput(item, COUNTRY, xstrdup(strsep(&value, ";")));
+
+       // support of optional trailing ";" to the ADR field
+       if(value && *value) xfree(value);
+}
+
+static void
+vcard_parse_name(list_item item, char *line)
+{
+       // store the "N" field into "NAME" *if* no "FN:"
+       // value has already been stored here
+       if(item[0]) return;
+
+       int i = -1;
+       item[0] = vcard_get_line_element(line, VCARD_VALUE);
+       // "N:" can be multivalued => replace ';' separators by ' '
+       while(item[0][++i]) if(item[0][i] == ';') item[0][i] = ' ';
+
+       // http://www.daniweb.com/software-development/c/code/216919
+       char *original = item[0], *p = original;
+       int trimmed = 0;
+       do {
+         if (*original != ' ' || trimmed) {
+           trimmed = 1; *p++ = *original;
+         }
+       } while(*original++);
+}
+
+static void
+vcard_parse_phone(list_item item, char *line)
+{
+       char *type = vcard_get_line_element(line, VCARD_KEY_ATTRIBUTE);
+       char *value = vcard_get_line_element(line, VCARD_VALUE);
+
+       /* set the standard number */
+       if (!type) item_fput(item, PHONE, value);
+
+       /*
+        * see rfc2426 section 3.3.1
+        * Note: we probably support both vCard 2 and 3
+        */
+       else {
+               if (strcasestr(type, "home") != NULL)
+                       item_fput(item, PHONE, xstrdup(value));
+               else if (strcasestr(type, "work") != NULL)
+                       item_fput(item, WORKPHONE, xstrdup(value));
+               else if (strcasestr(type, "fax") != NULL)
+                       item_fput(item, FAX, xstrdup(value));
+               else if (strcasestr(type, "cell") != NULL)
+                       item_fput(item, MOBILEPHONE, xstrdup(value));
+
+               xfree(type);
+               xfree(value);
+       }
+}
+
+static void
+vcard_parse_line(list_item item, char *line)
+{
+       int i;
+       char *key;
+
+       for(i=0; vcard_fields[i]; i++) {
+               key = vcard_fields[i];
+
+               if(0 == strncmp(key, line, strlen(key))) {
+                       if(0 == strcmp(key, "EMAIL"))
+                               vcard_parse_email(item, line);
+                       else if(i == 2)
+                               vcard_parse_address(item, line);
+                       else if(0 == strcmp(key, "TEL"))
+                               vcard_parse_phone(item, line);
+                       else if(0 == strcmp(key, "N"))
+                               vcard_parse_name(item, line);
+                       else
+                               item[i] = vcard_get_line_element(line, VCARD_VALUE);
+                       return;
+               }
+       }
+}
+
+static void
+vcard_parse_item(FILE *in)
+{
+       char *line = NULL;
+       list_item item = item_create();
+
+       while(!feof(in)) {
+               line = getaline(in);
+
+               if(line && !strncmp("END:VCARD", line, 9)) {
+                       xfree(line);
+                       break;
+               }
+               else if(line) {
+                       vcard_parse_line(item, line);
+                       xfree(line);
+               }
+       }
+
+       add_item2database(item);
+       item_free(&item);
+}
+
+static int
+vcard_parse_file(FILE *in)
+{
+       char *line = NULL;
+
+       while(!feof(in)) {
+               line = getaline(in);
+
+               if(line && !strncmp("BEGIN:VCARD", line, 11)) {
+                       xfree(line);
+                       vcard_parse_item(in);
+               }
+               else if(line) {
+                       xfree(line);
+               }
+       }
+
+       return 0;
+}
+
+/*
+ * end of vCard import filter
+ */
+
+/*
+ * csv addressbook export filters
+ */
+
+#define CSV_LAST               (-1)
+#define CSV_UNDEFINED          (-2)
+#define CSV_SPECIAL(X)         (-3 - (X))
+#define CSV_IS_SPECIAL(X)      ((X) <= -3)
+
+static int
+csv_export_common(FILE *out, struct db_enumerator e,
+               int fields[], void (*special_func)(FILE *, int, int))
+{
+       int i;
+
+       db_enumerate_items(e) {
+               for(i = 0; fields[i] != CSV_LAST; i++) {
+                       if(fields[i] == CSV_UNDEFINED)
+                               fprintf(out, "\"\"");
+                       else if(CSV_IS_SPECIAL(fields[i])) {
+                               if(special_func)
+                                       (*special_func)(out, e.item, fields[i]);
+                       }
+                       else if(fields[i] >= CUSTOM_FIELD_START_INDEX) {
+                               fprintf(out, "\"%s\"",
+                                       safe_str(db_fget_byid(e.item, fields[i] - CUSTOM_FIELD_START_INDEX)));
+                       }
+                       else
+                               /*fprintf(out,(
+                       strchr(safe_str(database[e.item][field_idx(fields[i])]), ',') ||
+                       strchr(safe_str(database[e.item][field_idx(fields[i])]), '\"')) ?
+                               "\"%s\"" : "%s",
+                               safe_str(database[e.item][field_idx(fields[i])])
+                               );*/
+                               fprintf(out, "\"%s\"",
+                                       safe_str(db_fget(e.item,fields[i])));
+
+                       if(fields[i + 1] != CSV_LAST)
+                               fputc(',', out);
+               }
+               fputc('\n', out);
+       }
+
+       return 0;
+}
+
+static int
+csv_export_database(FILE *out, struct db_enumerator e)
+{
+       int csv_export_fields[] = {
+               NAME,
+               EMAIL,
+               PHONE,
+               NOTES,
+               NICK,
+               CSV_LAST
+       };
+
+       csv_export_common(out, e, csv_export_fields, NULL);
+
+       return 0;
+}
+
+static int
+allcsv_export_database(FILE *out, struct db_enumerator e)
+{
+       /*
+        * TODO: Should get these atomatically from abook_fileds
+        *  - JH
+        */
+       int allcsv_export_fields[ITEM_FIELDS + 6] = { // only the 5 custom fields are allowed so far
+               NAME,
+               EMAIL,
+               ADDRESS,
+               ADDRESS2,
+               CITY,
+               STATE,
+               ZIP,
+               COUNTRY,
+               PHONE,
+               WORKPHONE,
+               FAX,
+               MOBILEPHONE, // spelt "mobile" in standard_fields
+               NICK,
+               URL,
+               NOTES,
+               ANNIVERSARY,
+               GROUPS,
+               CSV_LAST
+       };
+
+       fprintf(out, "#");
+       int i = 0;
+       while(allcsv_export_fields[i+1] != CSV_LAST) {
+               fprintf(out, "\"%s\",", standard_fields[i++].key);
+       }
+       fprintf(out, "\"%s\"", standard_fields[i].key);
+
+       /*
+         Custom fields handling:
+         This loop appends custom fields' id at the end of allcsv_export_fields and shift
+         the CSV_LAST sentinel value each time one is found.
+         CUSTOM_FIELD_START_INDEX is added to these index values so csv_export_common()
+         can later recognize them and call db_fget_byid() instead of the traditional db_fget()
+
+         It only search for defined the [legacy?] "custom" fields.
+       */
+
+       // pointer to the end of the field list
+       int append_field = ITEM_FIELDS;
+       // custom field's trailing number (between 1 and 5)
+       int j;
+       // full custom field name, eg "custom4"
+       char custom_field_key[8];
+       // index used by custom_field_key
+       int field_no;
+       // name of the defined field <field_no> as chosen by the user
+       char *custom_field_name;
+
+       for (j = 1; j <= 5; j++) {
+               snprintf(custom_field_key, 8, "custom%d", j++);
+               if(find_declared_field(custom_field_key)) {
+                       find_field_number(custom_field_key, &field_no);
+                       get_field_info(field_no, NULL, &custom_field_name, NULL);
+                       // append the field to the list
+                       allcsv_export_fields[append_field] = field_no + CUSTOM_FIELD_START_INDEX;
+                       allcsv_export_fields[++append_field] = CSV_LAST;
+                       // print column name
+                       fprintf(out, ",\"%s\"", custom_field_name);
+               }
+       }
+       free(custom_field_name);
+       fprintf(out, "\n");
+
+       csv_export_common(out, e, allcsv_export_fields, NULL);
+
+       return 0;
+}
+
+/*
+ * palm csv
+ */
+
+#define PALM_CSV_NAME  CSV_SPECIAL(0)
+#define PALM_CSV_END   CSV_SPECIAL(1)
+#define PALM_CSV_CAT   CSV_SPECIAL(2)
+
+static void
+palm_split_and_write_name(FILE *out, char *name)
+{
+       char *p;
+
+       assert(name);
+
+       if ( (p = strchr(name, ' ')) ) {
+               /*
+                * last name first
+                */
+               fprintf(out, "\"%s\",\"" , p + 1);
+               fwrite((void *)name, p - name, sizeof(char), out);
+               fputc('\"', out);
+       } else {
+               fprintf(out, "\"%s\"", safe_str(name));
+       }
+}
+
+static void
+palm_csv_handle_specials(FILE *out, int item, int field)
+{
+       switch(field) {
+               case PALM_CSV_NAME:
+                       palm_split_and_write_name(out, db_name_get(item));
+                       break;
+               case PALM_CSV_CAT:
+                       fprintf(out, "\"abook\"");
+                       break;
+               case PALM_CSV_END:
+                       fprintf(out, "\"0\"");
+                       break;
+               default:
+                       assert(0);
+       }
+}
+
+static int
+palm_export_database(FILE *out, struct db_enumerator e)
+{
+       int palm_export_fields[] = {
+               PALM_CSV_NAME,          /* LASTNAME, FIRSTNAME  */
+               CSV_UNDEFINED,          /* TITLE                */
+               CSV_UNDEFINED,          /* COMPANY              */
+               WORKPHONE,              /* WORK PHONE           */
+               PHONE,                  /* HOME PHONE           */
+               FAX,                    /* FAX                  */
+               MOBILEPHONE,            /* OTHER                */
+               EMAIL,                  /* EMAIL                */
+               ADDRESS,                /* ADDRESS              */
+               CITY,                   /* CITY                 */
+               STATE,                  /* STATE                */
+               ZIP,                    /* ZIP                  */
+               COUNTRY,                /* COUNTRY              */
+               NICK,                   /* DEFINED 1            */
+               URL,                    /* DEFINED 2            */
+               CSV_UNDEFINED,          /* DEFINED 3            */
+               CSV_UNDEFINED,          /* DEFINED 4            */
+               NOTES,                  /* NOTE                 */
+               PALM_CSV_END,           /* "0"                  */
+               PALM_CSV_CAT,           /* CATEGORY             */
+               CSV_LAST
+       };
+
+       csv_export_common(out, e, palm_export_fields, palm_csv_handle_specials);
+
+       return 0;
+}
+
+/*
+ * end of csv export filters
+ */
+
+/*
+ * vCard 2 addressbook export filter
+ */
+
+static int
+vcard_export_database(FILE *out, struct db_enumerator e)
+{
+  db_enumerate_items(e)
+    vcard_export_item(out, e.item);
+  return 0;
+}
+
+void
+vcard_export_item(FILE *out, int item)
+{
+       int j, email_no;
+       char *name, *tmp;
+       abook_list *emails, *em;
+       fprintf(out, "BEGIN:VCARD\r\nFN:%s\r\n",
+               safe_str(db_name_get(item)));
+
+       name = get_surname(db_name_get(item));
+       for( j = strlen(db_name_get(item)) - 1; j >= 0; j-- ) {
+         if((db_name_get(item))[j] == ' ')
+           break;
+       }
+       fprintf(out, "N:%s;%.*s\r\n",
+               safe_str(name),
+               j,
+               safe_str(db_name_get(item))
+               );
+
+       free(name);
+
+       if(db_fget(item, NICK))
+         fprintf(out, "NICKNAME:%s\r\n",
+                 safe_str(db_fget(item, NICK)));
+       if(db_fget(item, ANNIVERSARY))
+         fprintf(out, "BDAY:%s\r\n",
+                 safe_str(db_fget(item, ANNIVERSARY)));
+
+       // see rfc6350 section 6.3.1
+       if(db_fget(item, ADDRESS)) {
+               fprintf(out, "ADR:;%s;%s;%s;%s;%s;%s\r\n",
+                       // pobox (unsupported)
+                       safe_str(db_fget(item, ADDRESS2)), // ext (n°, ...)
+                       safe_str(db_fget(item, ADDRESS)), // street
+                       safe_str(db_fget(item, CITY)), // locality
+                       safe_str(db_fget(item, STATE)), // region
+                       safe_str(db_fget(item, ZIP)), // code (postal)
+                       safe_str(db_fget(item, COUNTRY)) // country
+                       );
+       }
+
+       if(db_fget(item, PHONE))
+         fprintf(out, "TEL;HOME:%s\r\n",
+                 db_fget(item, PHONE));
+       if(db_fget(item, WORKPHONE))
+         fprintf(out, "TEL;WORK:%s\r\n",
+                 db_fget(item, WORKPHONE));
+       if(db_fget(item, FAX))
+         fprintf(out, "TEL;FAX:%s\r\n",
+                 db_fget(item, FAX));
+       if(db_fget(item, MOBILEPHONE))
+         fprintf(out, "TEL;CELL:%s\r\n",
+                 db_fget(item, MOBILEPHONE));
+
+       tmp = db_email_get(item);
+       if(*tmp) {
+         emails = csv_to_abook_list(tmp);
+         fprintf(out, "EMAIL;PREF;INTERNET:%s\r\n", emails->data);
+         email_no = 1;
+         for(em = emails->next; em; em = em->next, email_no++ )
+                 fprintf(out, "EMAIL;%d;INTERNET:%s\r\n", email_no, em->data);
+
+         abook_list_free(&emails);
+       }
+       free(tmp);
+
+       if(db_fget(item, NOTES))
+         fprintf(out, "NOTE:%s\r\n",
+                 db_fget(item, NOTES));
+       if(db_fget(item, URL))
+         fprintf(out, "URL:%s\r\n",
+                 db_fget(item, URL));
+
+       fprintf(out, "END:VCARD\r\n\r\n");
+
+}
+
+/*
+ * end of vCard export filter
+ */
+
+
+/*
+ * mutt alias export filter
+ */
+
+static char *
+mutt_alias_genalias(int i)
+{
+       char *tmp, *pos;
+
+       if(db_fget(i, NICK))
+               return xstrdup(db_fget(i, NICK));
+
+       tmp = xstrdup(db_name_get(i));
+
+       if( ( pos = strchr(tmp, ' ') ) )
+               *pos = 0;
+
+       strlower(tmp);
+
+       return tmp;
+}
+
+/*
+ * This function is a variant of abook_list_to_csv
+ * */
+static char *
+mutt_alias_gengroups(int i)
+{
+       char *groups, *res = NULL;
+       char groupstr[7] = "-group ";
+       abook_list *list, *tmp;
+
+       groups = db_fget(i, GROUPS);
+
+       if(!groups)
+               return NULL;
+
+       list = csv_to_abook_list(groups);
+       for(tmp = list; tmp; tmp = tmp->next) {
+               if(tmp == list) {
+                       res = xmalloc(strlen(groupstr)+strlen(tmp->data)+1);
+                       res = strcpy(res, groupstr);
+               } else {
+                       res = xrealloc(res, strlen(res)+1+strlen(groupstr)+strlen(tmp->data)+1);
+                       strcat(res, " ");
+                       strcat(res, groupstr);
+               }
+               strcat(res, tmp->data);
+       }
+       abook_list_free(&list);
+       xfree(groups);
+
+       return res;
+}
+
+static int
+mutt_alias_export(FILE *out, struct db_enumerator e)
+{
+       char email[MAX_EMAIL_LEN];
+       char *alias = NULL;
+       char *groups = NULL;
+       int email_addresses;
+       char *ptr;
+
+       db_enumerate_items(e) {
+               alias = (field_id(NICK) != -1) ? mutt_alias_genalias(e.item) : NULL;
+               groups = (field_id(GROUPS) != -1) ?  mutt_alias_gengroups(e.item) : NULL;
+               get_first_email(email, e.item);
+
+               /* do not output contacts without email address */
+               /* cause this does not make sense in mutt aliases */
+               if (*email) {
+
+                       /* output first email address */
+                       fprintf(out,"alias ");
+                       if(groups)
+                               fprintf(out, "%s ", groups);
+                       if(alias)
+                               fprintf(out, "%s ", alias);
+                       fprintf(out, "%s <%s>\n",
+                                       db_name_get(e.item),
+                                       email);
+
+                       /* number of email addresses */
+                       email_addresses = 1;
+                       ptr = db_email_get(e.item);
+                       while (*ptr != '\0') {
+                               if (*ptr == ',') {
+                                       email_addresses++;
+                               }
+                               ptr++;
+                       }
+
+                       /* output other email addresses */
+                       while (email_addresses-- > 1) {
+                               roll_emails(e.item, ROTATE_RIGHT);
+                               get_first_email(email, e.item);
+                               fprintf(out,"alias ");
+                               if( groups )
+                                       fprintf(out, "%s ", groups);
+                               if(alias)
+                                       fprintf(out, "%s__%s ", alias, email);
+                               else
+                                       fprintf(out, "%s__%s ", db_name_get(e.item), email);
+                               fprintf(out, "%s <%s>\n",
+                                               db_name_get(e.item),
+                                               email);
+                       }
+                       roll_emails(e.item, ROTATE_RIGHT);
+                       xfree(alias);
+                       xfree(groups);
+               }
+       }
+
+       return 0;
+}
+
+void muttq_print_item(FILE *file, int item)
+{
+       abook_list *emails, *e;
+       char *tmp = db_email_get(item);
+
+       emails = csv_to_abook_list(tmp);
+       free(tmp);
+
+       for(e = emails; e; e = e->next) {
+               fprintf(file, "%s\t%s\t%s\n", e->data, db_name_get(item),
+                               !db_fget(item, NOTES) ?" " :db_fget(item, NOTES)
+                               );
+               if(!opt_get_bool(BOOL_MUTT_RETURN_ALL_EMAILS))
+                       break;
+       }
+       abook_list_free(&emails);
+}
+
+static int
+mutt_query_export_database(FILE *out, struct db_enumerator e)
+{
+  fprintf(out, "All items\n");
+  db_enumerate_items(e)
+    muttq_print_item(out, e.item);
+  return 0;
 }
 
 /*
@@ -1171,26 +2310,29 @@ mutt_alias_export(FILE *out, struct db_enumerator e)
 
 static void
 text_write_address_us(FILE *out, int i) {
-       fprintf(out, "\n%s", database[i][ADDRESS]);
-       
-       if (database[i][CITY])
-               fprintf(out, "\n%s", database[i][CITY]);
-               
-       if (database[i][STATE] || database[i][ZIP]) {
+       fprintf(out, "\n%s", db_fget(i, ADDRESS));
+
+       if(db_fget(i, ADDRESS2))
+               fprintf(out, "\n%s", db_fget(i, ADDRESS2));
+
+       if(db_fget(i, CITY))
+               fprintf(out, "\n%s", db_fget(i, CITY));
+
+       if(db_fget(i, STATE) || db_fget(i, ZIP)) {
                fputc('\n', out);
-               
-               if(database[i][STATE]) {
-                       fprintf(out, "%s", database[i][STATE]);
-                       if(database[i][ZIP])
+
+               if(db_fget(i, STATE)) {
+                       fprintf(out, "%s", db_fget(i, STATE));
+                       if(db_fget(i, ZIP))
                                fputc(' ', out);
                }
 
-               if(database[i][ZIP])
-                       fprintf(out, "%s", database[i][ZIP]);
+               if(db_fget(i, ZIP))
+                       fprintf(out, "%s", db_fget(i, ZIP));
        }
 
-       if (database[i][COUNTRY])
-               fprintf(out, "\n%s", database[i][COUNTRY]);
+       if(db_fget(i, COUNTRY))
+               fprintf(out, "\n%s", db_fget(i, COUNTRY));
 }
 
 
@@ -1198,48 +2340,44 @@ static void
 text_write_address_uk(FILE *out, int i) {
        int j;
 
-       for (j = ADDRESS; j <= COUNTRY; j++)
-               if (database[i][j])
-                       fprintf(out, "\n%s", database[i][j]);
+       for(j = ADDRESS; j <= COUNTRY; j++)
+               if(db_fget(i, j))
+                       fprintf(out, "\n%s", db_fget(i, j));
 }
 
 static void
 text_write_address_eu(FILE *out, int i) {
-       fprintf(out, "\n%s", database[i][ADDRESS]);
-       
-       if (database[i][ZIP] || database[i][CITY]) {
+       fprintf(out, "\n%s", db_fget(i, ADDRESS));
+
+       if(db_fget(i, ADDRESS2))
+               fprintf(out, "\n%s", db_fget(i, ADDRESS2));
+
+       if(db_fget(i, ZIP) || db_fget(i, CITY)) {
                fputc('\n', out);
 
-               if(database[i][ZIP]) {
-                       fprintf(out, "%s", database[i][ZIP]);
-                       if(database[i][CITY])
+               if(db_fget(i, ZIP)) {
+                       fprintf(out, "%s", db_fget(i, ZIP));
+                       if(db_fget(i, CITY))
                                fputc(' ', out);
                }
 
-               if(database[i][CITY])
-                       fprintf(out, "%s", database[i][CITY]);
-               /*
-               fprintf(out, "\n%s %s",
-                               safe_str(database[i][ZIP]),
-                               safe_str(database[i] [CITY]));
-                               */
+               fprintf(out, "%s", safe_str(db_fget(i, CITY)));
        }
-       
 
-       if (database[i][STATE])
-               fprintf(out, "\n%s", database[i][STATE]);
+       if(db_fget(i, STATE))
+               fprintf(out, "\n%s", db_fget(i, STATE));
 
-       if (database[i][COUNTRY])
-               fprintf(out, "\n%s", database[i][COUNTRY]);
+       if(db_fget(i, COUNTRY))
+               fprintf(out, "\n%s", db_fget(i, COUNTRY));
 }
 
 static int
 text_export_database(FILE * out, struct db_enumerator e)
 {
-       char emails[MAX_EMAILS][MAX_EMAIL_LEN];
+       abook_list *emails, *em;
        int j;
-       char *realname = get_real_name();
-       char *style = options_get_str("address_style");
+       char *realname = get_real_name(), *str = NULL, *tmp;
+       char *style = opt_get_str(STR_ADDRESS_STYLE);
 
        fprintf(out,
                "-----------------------------------------\n%s's address book\n"
@@ -1250,23 +2388,27 @@ text_export_database(FILE * out, struct db_enumerator e)
        db_enumerate_items(e) {
                fprintf(out,
                        "-----------------------------------------\n\n");
-               fprintf(out, "%s", database[e.item][NAME]);
-               if (database[e.item][NICK])
-                       fprintf(out, "\n(%s)", database[e.item][NICK]);
+               fprintf(out, "%s", db_name_get(e.item));
+               if(db_fget(e.item, NICK) && *db_fget(e.item, NICK))
+                       fprintf(out, "\n(%s)", db_fget(e.item, NICK));
                fprintf(out, "\n");
 
-               if (*database[e.item][EMAIL]) {
+               tmp = db_email_get(e.item);
+               if(*tmp) {
+                       emails = csv_to_abook_list(tmp);
+
                        fprintf(out, "\n");
-                       split_emailstr(e.item, emails);
-                       for (j = 0; j < MAX_EMAILS; j++)
-                               if (*emails[j])
-                                       fprintf(out, "%s\n", emails[j]);
+                       for(em = emails; em; em = em->next)
+                               fprintf(out, "%s\n", em->data);
+
+                       abook_list_free(&emails);
                }
+               free(tmp);
                /* Print address */
-               if (database[e.item][ADDRESS]) {
-                       if (!safe_strcmp(style, "us"))  /* US like */
+               if(db_fget(e.item, ADDRESS)) {
+                       if(!safe_strcmp(style, "us"))   /* US like */
                                text_write_address_us(out, e.item);
-                       else if (!safe_strcmp(style, "uk"))     /* UK like */
+                       else if(!safe_strcmp(style, "uk"))      /* UK like */
                                text_write_address_uk(out, e.item);
                        else    /* EU like */
                                text_write_address_eu(out, e.item);
@@ -1274,22 +2416,24 @@ text_export_database(FILE * out, struct db_enumerator e)
                        fprintf(out, "\n");
                }
 
-               if ((database[e.item][PHONE]) ||
-                       (database[e.item][WORKPHONE]) ||
-                       (database[e.item][FAX]) ||
-                       (database[e.item][MOBILEPHONE])) {
+               if((db_fget(e.item, PHONE)) ||
+                       (db_fget(e.item, WORKPHONE)) ||
+                       (db_fget(e.item, FAX)) ||
+                       (db_fget(e.item, MOBILEPHONE))) {
                        fprintf(out, "\n");
-                       for (j = PHONE; j <= MOBILEPHONE; j++)
-                               if (database[e.item][j])
-                                       fprintf(out, "%s: %s\n",
-                                               abook_fields[j].name,
-                                               database[e.item][j]);
+                       for(j = PHONE; j <= MOBILEPHONE; j++)
+                               if(db_fget(e.item, j)) {
+                                       get_field_info(field_id(j),
+                                                       NULL, &str, NULL);
+                                       fprintf(out, "%s: %s\n", str,
+                                               db_fget(e.item, j));
+                               }
                }
 
-               if (database[e.item][URL])
-                       fprintf(out, "\n%s\n", database[e.item][URL]);
-               if (database[e.item][NOTES])
-                       fprintf(out, "\n%s\n", database[e.item][NOTES]);
+               if(db_fget(e.item, URL))
+                       fprintf(out, "\n%s\n", db_fget(e.item, URL));
+               if(db_fget(e.item, NOTES))
+                       fprintf(out, "\n%s\n", db_fget(e.item, NOTES));
 
                fprintf(out, "\n");
        }
@@ -1316,11 +2460,8 @@ elm_alias_export(FILE *out, struct db_enumerator e)
        db_enumerate_items(e) {
                alias = mutt_alias_genalias(e.item);
                get_first_email(email, e.item);
-               fprintf(out, "%s = %s = %s\n",
-                               alias,
-                               database[e.item][NAME],
-                               email);
-               my_free(alias);
+               fprintf(out, "%s = %s = %s\n",alias,db_name_get(e.item),email);
+               xfree(alias);
        }
 
        return 0;
@@ -1340,16 +2481,16 @@ spruce_export_database (FILE *out, struct db_enumerator e)
 {
        char email[MAX_EMAIL_LEN];
 
-       fprintf (out, "# This is a generated file made by abook for the Spruce e-mail client.\n\n");
+       fprintf(out, "# This is a generated file made by abook for the Spruce e-mail client.\n\n");
 
        db_enumerate_items(e) {
-               if(strcmp (safe_str(database[e.item][EMAIL]), "")) {
-                       get_first_email(email, e.item);
+               get_first_email(email, e.item);
+               if(strcmp(email, "")) {
                        fprintf(out, "# Address %d\nName: %s\nEmail: %s\nMemo: %s\n\n",
                                        e.item,
-                                       database[e.item][NAME],
+                                       db_name_get(e.item),
                                        email,
-                                       safe_str(database[e.item][NOTES])
+                                       safe_str(db_fget(e.item, NOTES))
                                        );
                }
        }
@@ -1363,3 +2504,240 @@ spruce_export_database (FILE *out, struct db_enumerator e)
  * end of Spruce export filter
  */
 
+/*
+ * wanderlust addressbook export filter
+ */
+
+static int
+wl_export_database(FILE *out, struct db_enumerator e)
+{
+       char email[MAX_EMAIL_LEN];
+
+       fprintf(out, "# Wanderlust address book written by %s\n\n", PACKAGE);
+       db_enumerate_items(e) {
+               get_first_email(email, e.item);
+               if(*email) {
+                       fprintf(out,
+                               "%s\t\"%s\"\t\"%s\"\n",
+                               email,
+                               safe_str(db_fget(e.item, NICK)),
+                               safe_str(db_name_get(e.item))
+                       );
+               }
+       }
+
+       fprintf (out, "\n# End of address book file.\n");
+
+       return 0;
+}
+
+/*
+ * end of wanderlust addressbook export filter
+ */
+
+/*
+ * BSD calendar export filter
+ */
+
+static int
+bsdcal_export_database(FILE *out, struct db_enumerator e)
+{
+       db_enumerate_items(e) {
+               int year, month = 0, day = 0;
+               char *anniversary = db_fget(e.item, ANNIVERSARY);
+
+               if(anniversary) {
+                       if(!parse_date_string(anniversary, &day, &month, &year))
+                               continue;
+
+                       fprintf(out,
+                               _("%02d/%02d\tAnniversary of %s\n"),
+                               month,
+                               day,
+                               safe_str(db_name_get(e.item))
+                       );
+               }
+       }
+
+       return 0;
+}
+
+static int
+find_field_enum(char *s) {
+       int i = -1;
+       while(standard_fields[++i].key)
+               if(!strcmp(standard_fields[i].key, s))
+                       return i;
+       return -1;
+}
+
+/* Convert a string with named placeholders to
+   a *printf() compatible string.
+   Stores the abook field values into ft. */
+void
+parse_custom_format(char *s, char *fmt_string, enum field_types *ft)
+{
+       if(! fmt_string || ! ft) {
+         fprintf(stderr, _("parse_custom_format: fmt_string or ft not allocated\n"));
+         exit(EXIT_FAILURE);
+       }
+
+       char tmp[1] = { 0 };
+       char *p, *start, *field_name = NULL;
+       p = start = s;
+
+       while(*p) {
+               if(*p == '{') {
+                 start = ++p;
+
+                 if(! *start) goto cannotparse;
+                 p = strchr(start, '}');
+                 if(! p) goto cannotparse;
+                 strcat(fmt_string, "%s");
+                 field_name = strndup(start, (size_t)(p-start));
+                 *ft = find_field_enum(field_name);
+                 if(*ft == -1) {
+                   fprintf(stderr, _("parse_custom_format: invalid placeholder: {%s}\n"), field_name);
+                   exit(EXIT_FAILURE);
+                 }
+
+                 ft++;
+                 start = ++p;
+               }
+
+               else if(*p == '\\') {
+                       ++p;
+                       if(! *p) tmp[0] = '\\'; // last char is a '\' ?
+                       else if(*p == 'n') *tmp = '\n';
+                       else if(*p == 't') *tmp = '\t';
+                       else if(*p == 'r') *tmp = '\r';
+                       else if(*p == 'v') *tmp = '\v';
+                       else if(*p == 'b') *tmp = '\b';
+                       else if(*p == 'a') *tmp = '\a';
+                       else *tmp = *p;
+                       strncat(fmt_string, tmp, 1);
+                       start = ++p;
+               }
+
+               // if no '\' following: quick mode using strchr/strncat
+               else if(! strchr(start, '\\')) {
+                 p = strchr(start, '{');
+                 if(p) { // copy until the next placeholder
+                   strncat(fmt_string, start, (size_t)(p-start));
+                   start = p;
+                 }
+                 else { // copy till the end
+                   strncat( fmt_string,
+                            start,
+                            FORMAT_STRING_LEN - strlen(fmt_string) - 1 );
+                   break;
+                 }
+               }
+
+               // otherwise character by character
+               else {
+                       strncat(fmt_string, p, 1);
+                       start = ++p;
+               }
+       }
+
+       *ft = ITEM_FIELDS;
+       return;
+
+ cannotparse:
+       fprintf(stderr, _("%s: invalid format, index %ld\n"), __FUNCTION__, (start - s));
+       free(fmt_string);
+       free(ft);
+       exit(EXIT_FAILURE);
+}
+
+static int
+custom_export_item(FILE *out, int item, char *s, enum field_types *ft);
+
+
+// used to store the format string from --outformatstr when "custom" format is used
+// default value overriden in export_file()
+extern char *parsed_custom_format;
+extern enum field_types *custom_format_fields;
+
+/* wrapper for custom_export_item:
+   1) avoid messing with extern pointer
+   2) adds \n
+   3) follow the prototype needed for an abook_output_item_filter entry */
+void
+custom_print_item(FILE *out, int item)
+{
+
+  if(custom_export_item(out, item, parsed_custom_format, custom_format_fields) == 0)
+    fprintf(out, "\n");
+}
+
+static int
+custom_export_item(FILE *out, int item, char *fmt, enum field_types *ft)
+{
+  char *p, *q = 0;
+
+  // if the first character is '!':
+  // we first check that all fields exist before continuing
+  if(*fmt == '!') {
+    enum field_types *ftp = ft;
+    while(*ft != ITEM_FIELDS) {
+      if(! db_fget(item, *ft) )
+       return 1;
+      ft++;
+    }
+    ft = ftp;
+    fmt++;
+  }
+
+  while (*fmt) {
+    if(!strncmp(fmt, "%s", 2)) {
+      fprintf(out, "%s", safe_str(db_fget(item, *ft)));
+      ft++;
+      fmt+=2;
+    } else if (*ft == ITEM_FIELDS) {
+      fprintf(out, "%s", fmt);
+      return 0;
+    } else {
+      p = strchr(fmt, '%');
+      if(*p) {
+       q = strndup(fmt, (size_t)(p-fmt));
+       fprintf(out, "%s", q);
+       free(q);
+       fmt = p;
+      }
+      else {
+       fprintf(out, "%s", fmt);
+       return 0;
+      }
+    }
+  }
+
+  return 0;
+}
+
+// used to store the format string from --outformatstr when "custom" format is used
+// default value overriden from abook.c
+extern char custom_format[FORMAT_STRING_LEN];
+
+static int
+custom_export_database(FILE *out, struct db_enumerator e)
+{
+       char *format_string = (char *)malloc(FORMAT_STRING_LEN);
+
+       enum field_types *ft =
+         (enum field_types *)malloc(FORMAT_STRING_MAX_FIELDS * sizeof(enum field_types));
+
+       parse_custom_format(custom_format, format_string, ft);
+
+       db_enumerate_items(e) {
+         if(custom_export_item(out, e.item, format_string, ft) == 0)
+           fprintf(out, "\n");
+       }
+       return 0;
+}
+
+/*
+ * end of BSD calendar export filter
+ */
+