X-Git-Url: https://git.deb.at/w?p=pkg%2Fabook.git;a=blobdiff_plain;f=filter.c;h=dca51b804e79495580c3af03dedfcf477e7a0cf4;hp=4489839470e5b2eb6a9957c3a5d6a06ce083014c;hb=b722eae92e8deb6bd080dea1f2f929a7dd9edddb;hpb=12a57d9405d7910566d5ff888d743d29b0716554 diff --git a/filter.c b/filter.c index 4489839..dca51b8 100644 --- a/filter.c +++ b/filter.c @@ -2,71 +2,129 @@ /* * $Id$ * - * by JH + * by JH * * Copyright (C) Jaakko Heinonen */ +#define _GNU_SOURCE + #include #include #include #include #include +#include #include #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 + +#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 */ +/* + * import filter prototypes + */ + 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 + */ + +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 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 + */ -static int ldif_export_database(FILE *out); -static int html_export_database(FILE *out); -static int pine_export_database(FILE *out); -static int csv_export_database(FILE *out); -static int gcrd_export_database(FILE *out); -static int mutt_alias_export(FILE *out); -static int elm_alias_export(FILE *out); -static int text_export_database(FILE *out); -static int spruce_export_database(FILE *out); - - -struct abook_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 }, +struct abook_input_filter i_filters[] = { + { "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_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 }, +struct abook_output_filter e_filters[] = { + { "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 */ @@ -75,28 +133,46 @@ 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'); } static int -number_of_filters(struct abook_filter * filters) +number_of_output_filters() +{ + int i; + + for(i=0; *e_filters[i].filtname ; i++) + ; + + return i; +} + +static int +number_of_input_filters() { int i; - for(i=0; *filters[i].filtname ; i++) + for(i=0; *i_filters[i].filtname ; i++) ; return i; @@ -112,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; } @@ -126,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 @@ -155,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_filters(e_filters) || - filter < 0) { + 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); @@ -194,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; } @@ -238,122 +330,167 @@ import(char filtname[FILTNAME_LEN], char *filename) * export */ -static int e_write_file(char *filename, int (*func) (FILE *in)); +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 export_database() { int filter; + int enum_mode = ENUM_ALL; char *filename; export_screen(); - + filter = getch() - 'a'; - if(filter == 'x' - 'a' || filter >= number_of_filters(e_filters) || - filter < 0) { + if(filter == 'x' - 'a' || + filter >= number_of_output_filters() || filter < 0) { refresh_screen(); return 1; } - - mvaddstr(5+filter, 2, "->"); - - filename = ask_filename("Filename: ", 0); - if( !filename ) { + + mvaddstr(5 + filter, 2, "->"); + + if(selected_items()) { + switch(statusline_askchoice( + _("Export ll, export elected, or ancel?"), + S_("keybindings:all/selected/cancel|asc"), 3)) { + case 1: + break; + case 2: + enum_mode = ENUM_SELECTED; + break; + case 0: + case 3: + refresh_screen(); + return 1; + } + clear_statusline(); + } + + filename = ask_filename(_("Filename: ")); + if(!filename) { refresh_screen(); return 2; } - - if( e_write_file(filename, e_filters[filter].func ) ) - 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)) +e_write_file(char *filename, int (*func) (FILE *in, struct db_enumerator e), + int mode) { FILE *out; 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); - + ret = (*func) (out, enumerator); + fclose(out); - + return ret; } int -fexport(char filtname[FILTNAME_LEN], FILE *handle) +fexport(char filtname[FILTNAME_LEN], FILE *handle, int enum_mode) { int i; + 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; } } - return (e_filters[i].func) (handle); + 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, "-") ) - ret = (e_filters[i].func) (stdout); + if(!strcmp(filename, "-")) + ret = (e_filters[i].func) (stdout, e); else - ret = e_write_file(filename, e_filters[i].func); + ret = e_write_file(filename, e_filters[i].func, mode); return ret; } @@ -368,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; } } } @@ -515,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); + + // EOF or empty lines: continue; + if(!line || *line == '\0') continue; - if( -1 == ( str_parse_line(line, &type, &value, &vlen)) ) { - my_free(line); + 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 */ @@ -563,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; - - 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); + char *escape = 0; - 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; } @@ -699,6 +962,7 @@ mutt_parse_file(FILE *in) * end of mutt alias import filter */ + /* * ldif export filter */ @@ -716,30 +980,44 @@ ldif_fput_type_and_value(FILE *out,char *type, char *value ) } static int -ldif_export_database(FILE *out) +ldif_export_database(FILE *out, struct db_enumerator e) { - int i; char email[MAX_EMAILSTR_LEN]; + abook_list *emails, *em; fprintf(out, "version: 1\n"); - for(i=0; i= 0) { - if(ldif_conv_table[j] == EMAIL) - ldif_fput_type_and_value(out, - ldif_field_names[j], email); - else if(database[i][ldif_conv_table[j]]) - ldif_fput_type_and_value(out, - ldif_field_names[j], - database[i][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)); } } @@ -758,54 +1036,85 @@ ldif_export_database(FILE *out) * 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, "%s", l->data, l->data); + if(l->next) + fprintf(out, ", "); + } + + abook_list_free(&l); +} + static int -html_export_database(FILE *out) +html_export_database(FILE *out, struct db_enumerator e) { - char tmp[MAX_EMAILSTR_LEN]; - int i; - 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; - - html_export_write_head(out, extra_column); - for( i = 0; i < items; i++ ) { - get_first_email(tmp, i); - fprintf(out, "\n%s\n", - tmp, - database[i][NAME] ); - - fprintf(out, "%s\n%s\n", - database[i][EMAIL], - safe_str(database[i][extra_column]) ); - fprintf(out, "\n\n"); + init_index(); + + html_export_write_head(out); + + db_enumerate_items(e) { + fprintf(out, ""); + for(cur = index_elements; cur; cur = cur->next) { + if(cur->type != INDEX_FIELD) + continue; + + get_list_field(e.item, cur, &f); + + if(f.type == FIELD_EMAILS) { + fprintf(out, ""); + html_print_emails(out, &f); + fprintf(out, ""); + continue; + } else { + fprintf(out, "%s", safe_str(f.data)); + } + } + fprintf(out, "\n"); } + html_export_write_tail(out); 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, "\n\n %s's addressbook", + fprintf(out, "\n"); + fprintf(out, "\n\n %s's addressbook", realname ); fprintf(out, "\n\n\n"); fprintf(out, "\n

%s's addressbook

\n", realname ); fprintf(out, "

\n\n"); - fprintf(out, "
\n"); - fprintf(out, "\n\n\n", - abook_fields[extra_column].name); + fprintf(out, "
NameE-mail address(es)%s
\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, "", str); + } + fprintf(out, "\n\n"); free(realname); } @@ -813,24 +1122,27 @@ html_export_write_head(FILE *out, int extra_column) static void html_export_write_tail(FILE *out) { - fprintf(out, "\n
%s
\n"); + fprintf(out, "\n\n"); fprintf(out, "\n\n\n"); } - + /* * end of html export filter */ + /* * 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]; } @@ -840,19 +1152,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; } } @@ -863,32 +1175,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 @@ -896,33 +1211,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; @@ -932,6 +1250,7 @@ pine_parse_file(FILE *in) * end of pine addressbook import filter */ + /* * pine addressbook export filter * @@ -940,18 +1259,20 @@ pine_parse_file(FILE *in) */ static int -pine_export_database(FILE *out) +pine_export_database(FILE *out, struct db_enumerator e) { - int i; + char *emails; - for(i=0; i < items; i++ ) { - fprintf(out, have_multiple_emails(i) ? + db_enumerate_items(e) { + 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[i][NICK]), - safe_str(database[i][NAME]), - safe_str(database[i][EMAIL]), - safe_str(database[i][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; @@ -963,158 +1284,993 @@ pine_export_database(FILE *out) /* - * csv addressbook export filter + * csv import filter */ -static int -csv_export_database(FILE *out) -{ - int i, j; - int csv_export_fields[] = { - NAME, - EMAIL, - PHONE, - NOTES, - -1 - }; - - for(i=0; i < items; i++ ) { - for(j = 0; csv_export_fields[j] >= 0; j++) { - fprintf(out, strchr(safe_str(database[i][csv_export_fields[j]]), ',') ? - "\"%s\"" : "%s", - safe_str(database[i][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) -{ - char emails[MAX_EMAILS][MAX_EMAIL_LEN]; - int i, j; - char *name; - - for(i=0; i < items; i++ ) { - fprintf(out, "BEGIN:VCARD\nFN:%s\n", - safe_str(database[i][NAME])); - - name = get_surname(database[i][NAME]); - for( j = strlen(database[i][NAME]) - 1; j >= 0; j-- ) { - if(database[i][NAME][j] == ' ') - break; - } - fprintf(out, "N:%s;%.*s\n", - safe_str(name), - j, - safe_str(database[i][NAME]) - ); +static void +csv_convert_emails(char *s) +{ + int i; + char *tmp; - free(name); + if(s == NULL) + return; - if ( database[i][ADDRESS] ) - fprintf(out, "ADR:;;%s;%s;%s;%s;%s\n", - safe_str(database[i][ADDRESS]), - safe_str(database[i][CITY]), - safe_str(database[i][STATE]), - safe_str(database[i][ZIP]), - safe_str(database[i][COUNTRY]) - ); - - if (database[i][PHONE]) - fprintf(out, "TEL;HOME:%s\n", database[i][PHONE]); - if (database[i][WORKPHONE]) - fprintf(out, "TEL;WORK:%s\n", database[i][WORKPHONE]); - if (database[i][FAX]) - fprintf(out, "TEL;FAX:%s\n", database[i][FAX]); - if (database[i][MOBILEPHONE]) - fprintf(out, "TEL;CELL:%s\n", database[i][MOBILEPHONE]); - - if ( database[i][EMAIL] ) { - split_emailstr(i, 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[i][NOTES] ) - fprintf(out, "NOTE:%s\n", database[i][NOTES]); - if (database[i][URL]) - fprintf(out, "URL:%s\n", database[i][URL]); - - fprintf(out, "END:VCARD\n\n"); - - } - return 0; } -/* - * end of GnomeCard export filter - */ +static char * +csv_remove_quotes(char *s) +{ + char *copy, *trimmed; + int len; + copy = trimmed = xstrdup(s); + strtrim(trimmed); -/* - * mutt alias export filter - */ + 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; + } + + xfree(copy); + return xstrdup(s); +} + +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]); + + return -1; +} + +static void +csv_store_item(list_item item, int i, char *s) +{ + char *newstr = NULL; + + if(!s || !*s) + return; + + if( !(newstr = csv_remove_quotes(s)) ) + return; + + if(i >= 0) { + if (item[i] != NULL) { + char *oldstr = item[i]; + + item[i] = strconcat(newstr, CSV_DUPLICATE_SEPARATOR, + oldstr, NULL); + xfree(newstr); + xfree(oldstr); + } else { + item[i] = newstr; + } + } else { + xfree(newstr); + } +} + +static int +csv_is_valid_quote_end(char *p) +{ + if(*p != '\"') + return FALSE; + + for(p++; *p; p++) { + if(*p == ',') + return TRUE; + else if(!ISSPACE(*p)) + return FALSE; + } + + return TRUE; +} + +static int +csv_is_valid_quote_start(char *p) +{ + for(; *p; p++) { + if(*p == '\"') + return TRUE; + else if(!ISSPACE(*p)) + return FALSE; + } + + 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 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(database[i][NICK]) - return strdup(database[i][NICK]); - tmp = strdup(database[i][NAME]); + 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; + 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) +mutt_alias_export(FILE *out, struct db_enumerator e) { - int i; char email[MAX_EMAIL_LEN]; char *alias = NULL; + char *groups = NULL; + int email_addresses; + char *ptr; - for(i=0; i\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++; + } - get_first_email(email, i); - fprintf(out, *email ? "alias %s %s <%s>\n": "alias %s %s%s\n", - alias, - database[i][NAME], - email); - my_free(alias); + /* 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; +} + /* * end of mutt alias export filter */ @@ -1124,13 +2280,77 @@ mutt_alias_export(FILE *out) * printable export filter */ + +static void +text_write_address_us(FILE *out, int i) { + 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(db_fget(i, STATE)) { + fprintf(out, "%s", db_fget(i, STATE)); + if(db_fget(i, ZIP)) + fputc(' ', out); + } + + if(db_fget(i, ZIP)) + fprintf(out, "%s", db_fget(i, ZIP)); + } + + if(db_fget(i, COUNTRY)) + fprintf(out, "\n%s", db_fget(i, COUNTRY)); +} + + +static void +text_write_address_uk(FILE *out, int i) { + int 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", 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(db_fget(i, ZIP)) { + fprintf(out, "%s", db_fget(i, ZIP)); + if(db_fget(i, CITY)) + fputc(' ', out); + } + + fprintf(out, "%s", safe_str(db_fget(i, CITY))); + } + + if(db_fget(i, STATE)) + fprintf(out, "\n%s", db_fget(i, STATE)); + + if(db_fget(i, COUNTRY)) + fprintf(out, "\n%s", db_fget(i, COUNTRY)); +} + static int -text_export_database(FILE * out) +text_export_database(FILE * out, struct db_enumerator e) { - char emails[MAX_EMAILS][MAX_EMAIL_LEN]; - int i, j; - char *realname = get_real_name(); - char *style = options_get_str("address_style"); + abook_list *emails, *em; + int j; + char *realname = get_real_name(), *str = NULL, *tmp; + char *style = opt_get_str(STR_ADDRESS_STYLE); fprintf(out, "-----------------------------------------\n%s's address book\n" @@ -1138,76 +2358,55 @@ text_export_database(FILE * out) realname); free(realname); - for (i = 0; i < items; i++) { + db_enumerate_items(e) { fprintf(out, "-----------------------------------------\n\n"); - fprintf(out, "%s", database[i][NAME]); - if (database[i][NICK]) - fprintf(out, "\n(%s)", database[i][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[i][EMAIL]) { + tmp = db_email_get(e.item); + if(*tmp) { + emails = csv_to_abook_list(tmp); + fprintf(out, "\n"); - split_emailstr(i, 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[i][ADDRESS]) { - if (!safe_strcmp(style, "us")) { /* US like */ - 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 %s", - safe_str(database[i] - [STATE]), - safe_str(database[i] - [ZIP])); - if (database[i][COUNTRY]) - fprintf(out, "\n%s", - database[i][COUNTRY]); - } - - else if (!safe_strcmp(style, "uk")) { /* UK like */ - for (j = ADDRESS; j <= COUNTRY; j++) - if (database[i][j]) - fprintf(out, "\n%s", - database[i][j]); - } else { /* EU like */ - fprintf(out, "\n%s", database[i][ADDRESS]); - if (database[i][ZIP] || database[i][CITY]) - fprintf(out, "\n%s %s", - safe_str(database[i][ZIP]), - safe_str(database[i] - [CITY])); - if (database[i][STATE]) - fprintf(out, "\n%s", - database[i][STATE]); - if (database[i][COUNTRY]) - fprintf(out, "\n%s", - database[i][COUNTRY]); - } + 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 */ + text_write_address_uk(out, e.item); + else /* EU like */ + text_write_address_eu(out, e.item); fprintf(out, "\n"); } - if ((database[i][PHONE]) || (database[i][WORKPHONE]) || - (database[i][FAX]) || (database[i][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[i][j]) - fprintf(out, "%s: %s\n", - abook_fields[j].name, - database[i][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[i][URL]) - fprintf(out, "\n%s\n", database[i][URL]); - if (database[i][NOTES]) - fprintf(out, "\n%s\n", database[i][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"); } @@ -1226,20 +2425,16 @@ text_export_database(FILE * out) */ static int -elm_alias_export(FILE *out) +elm_alias_export(FILE *out, struct db_enumerator e) { - int i; char email[MAX_EMAIL_LEN]; char *alias = NULL; - for(i=0; i