X-Git-Url: https://git.deb.at/w?p=pkg%2Fabook.git;a=blobdiff_plain;f=filter.c;h=dca51b804e79495580c3af03dedfcf477e7a0cf4;hp=a22d69edf3bd94c82585b6415d5783449033c44a;hb=b722eae92e8deb6bd080dea1f2f929a7dd9edddb;hpb=0c57d01df43a4d9d02456d694c2a0cd6cb80a637 diff --git a/filter.c b/filter.c index a22d69e..dca51b8 100644 --- a/filter.c +++ b/filter.c @@ -29,8 +29,14 @@ #include "xmalloc.h" #include +#ifdef HAVE_VFORMAT +#include "vcard.h" +#endif + extern abook_field_list *fields_list; extern int fields_count; +// see also enum field_types @database.h +extern abook_field standard_fields[]; /* * function declarations @@ -60,11 +66,21 @@ 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 @@ -87,6 +103,7 @@ struct abook_output_filter e_filters[] = { { "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 }, @@ -97,9 +114,17 @@ struct abook_output_filter e_filters[] = { { "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 */ @@ -109,19 +134,26 @@ 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, 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, 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 @@ -267,6 +299,18 @@ import_file(char filtname[FILTNAME_LEN], char *filename) if(i < 0) return -1; +#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)) @@ -361,6 +405,25 @@ export_database() 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) @@ -442,79 +505,151 @@ export_file(char filtname[FILTNAME_LEN], char *filename) #include "ldif.h" -static void ldif_fix_string(char *str); +/* 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 16 +#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", - "streetaddress2", - "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" */ - ADDRESS2, /* "streetaddress2" */ - 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); - +/* + 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) +ldif_read_line(FILE *in, char **next_line) { char *buf = NULL; char *ptr, *tmp; - long pos; - int i; + char *line; - for(i = 1;;i++) { - char *line; + // buf filled with the first line + if(!*next_line) + buf = getaline(in); + else { + buf = xstrdup(*next_line); + xfree(*next_line); + } - pos = ftell(in); + while(!feof(in)) { + // if no line already read-ahead. line = getaline(in); + if(!line) break; - if(feof(in) || !line) - break; - - if(i == 1) { - buf = line; - continue; - } - + // this is not a continuation of what is already in buf + // store it for the next round if(*line != ' ') { - fseek(in, pos, SEEK_SET); /* fixme ! */ - free(line); + *next_line = line; break; } + // starts with ' ': this is the continuation of buf ptr = line; while( *ptr == ' ') ptr++; @@ -539,46 +674,90 @@ ldif_add_item(ldif_item li) list_item item; int i; - item = item_create(); - - if(!li[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 && li[i] && *li[i]) - item_fput(item,ldif_conv_table[i],xstrdup(li[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(item); + item_free(&item); bail_out: for(i=0; i < LDIF_ITEM_FIELDS; i++) xfree(li[i]); - item_free(&item); - } static void ldif_convert(ldif_item item, char *type, char *value) { - int i; - + /* 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; + 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)); + } + } - if(item_fget(item, i)) - free(item_fget(item, i)); + /* ... 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); + } - item_fput(item, i, xstrdup(value)); + // matching attr found and field filled: + // no further attr search is needed for `type` + break; } } } @@ -587,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))) { xfree(line); continue; /* just skip the errors */ } - ldif_fix_string(value); - ldif_convert(item, type, value); 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 < (int)strlen(str); i++, j++) - str[i] = ( str[j] == (char)0xc3 ? - (char) str[++j] + (char) 0x40 : - str[j] ); - - str[i] = 0; -} - /* * end of ldif import */ @@ -638,10 +811,11 @@ ldif_fix_string(char *str) #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, *ptr, *tmp; - size_t alias_len; + char *line, *ptr; + char *start, *end; + abook_list *glist = NULL; if( !(line = ptr = getaline(in)) ) return 1; /* error / EOF */ @@ -654,27 +828,39 @@ mutt_read_line(FILE *in, char **alias, char **rest) } ptr += 5; - SKIPWS(ptr); - tmp = ptr; - - while( ! ISSPACE(*ptr) ) - ptr++; - - alias_len = (size_t)(ptr - tmp); + /* 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); + } - if(alias) - *alias = xmalloc_inc(alias_len, 1); + if(n_groups && groups) + *groups = abook_list_to_csv(glist); - strncpy(*alias, tmp, alias_len); - *(*alias + alias_len) = 0; + abook_list_free(&glist); + /* alias */ + start = ptr; + SKIPNONWS(ptr); + end = ptr; SKIPWS(ptr); + if(alias) + *alias = xstrndup(start, end - start); + /* rest (email) */ *rest = xstrdup(ptr); - free(line); + xfree(line); return 0; } @@ -755,9 +941,9 @@ mutt_parse_file(FILE *in) memset(item, 0, fields_count * sizeof(char *)); if(!mutt_read_line(in, - (field_id(NICK) != -1) ? - &item[field_id(NICK)] : NULL, - &item[field_id(NAME)])) + (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)) { @@ -797,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"); @@ -805,21 +992,32 @@ ldif_export_database(FILE *out, struct db_enumerator e) int j; get_first_email(email, e.item); - tmp = strdup_printf("cn=%s,mail=%s",db_name_get(e.item),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(db_fget(e.item,ldif_conv_table[j])) - ldif_fput_type_and_value(out, - ldif_field_names[j], - db_fget(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)); } } @@ -1089,6 +1287,11 @@ pine_export_database(FILE *out, struct db_enumerator e) * csv import filter */ +/* 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 @@ -1342,7 +1545,7 @@ static char *vcard_fields[] = { "FN", /* FORMATTED NAME */ "EMAIL", /* EMAIL */ "ADR", /* ADDRESS */ - "ADR", /* ADDRESS2 - not used */ + "ADR", /* ADDRESS2 */ "ADR", /* CITY */ "ADR", /* STATE */ "ADR", /* ZIP */ @@ -1354,22 +1557,9 @@ static char *vcard_fields[] = { "NICKNAME", /* NICK */ "URL", /* URL */ "NOTE", /* NOTES */ + "BDAY", /* ANNIVERSARY */ "N", /* NAME: special case/mapping in vcard_parse_line() */ - NULL /* not implemented: ANNIVERSARY, ITEM_FIELDS */ -}; - -/* - * mappings between vCard ADR field and abook's ADDRESS - * see rfc2426 section 3.2.1 - */ -static int vcard_address_fields[] = { - -1, /* vCard(post office box) - not used */ - -1, /* vCard(the extended address) - not used */ - 2, /* vCard(the street address) - ADDRESS */ - 4, /* vCard(the locality) - CITY */ - 5, /* vCard(the region) - STATE */ - 6, /* vCard(the postal code) - ZIP */ - 7 /* vCard(the country name) - COUNTRY */ + NULL /* ITEM_FIELDS */ }; enum { @@ -1455,33 +1645,49 @@ vcard_parse_email(list_item item, char *line) } } + +/* + * 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) { - int i; - int k; char *value; - char *address_field; value = vcard_get_line_element(line, VCARD_VALUE); if(!value) return; - address_field = value; - for(i=k=0; value[i]; i++) { - if(value[i] == ';') { - value[i] = '\0'; - if(vcard_address_fields[k] >= 0) { - item[vcard_address_fields[k]] = xstrdup(address_field); - } - address_field = &value[i+1]; - k++; - if((k+1)==(sizeof(vcard_address_fields)/sizeof(*vcard_address_fields))) - break; - } - } - item[vcard_address_fields[k]] = xstrdup(address_field); - xfree(value); + // 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 @@ -1628,7 +1834,12 @@ csv_export_common(FILE *out, struct db_enumerator e, else if(CSV_IS_SPECIAL(fields[i])) { if(special_func) (*special_func)(out, e.item, fields[i]); - } else + } + 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])]), '\"')) ? @@ -1671,7 +1882,7 @@ allcsv_export_database(FILE *out, struct db_enumerator e) * TODO: Should get these atomatically from abook_fileds * - JH */ - int allcsv_export_fields[] = { + int allcsv_export_fields[ITEM_FIELDS + 6] = { // only the 5 custom fields are allowed so far NAME, EMAIL, ADDRESS, @@ -1683,31 +1894,57 @@ allcsv_export_database(FILE *out, struct db_enumerator e) PHONE, WORKPHONE, FAX, - MOBILEPHONE, + MOBILEPHONE, // spelt "mobile" in standard_fields NICK, URL, NOTES, ANNIVERSARY, + GROUPS, CSV_LAST }; fprintf(out, "#"); - fprintf(out, "\"NAME\","); - fprintf(out, "\"EMAIL\","); - fprintf(out, "\"ADDRESS\","); - fprintf(out, "\"ADDRESS2\","); - fprintf(out, "\"CITY\","); - fprintf(out, "\"STATE\","); - fprintf(out, "\"ZIP\","); - fprintf(out, "\"COUNTRY\","); - fprintf(out, "\"PHONE\","); - fprintf(out, "\"WORKPHONE\","); - fprintf(out, "\"FAX\","); - fprintf(out, "\"MOBILEPHONE\","); - fprintf(out, "\"NICK\","); - fprintf(out, "\"URL\","); - fprintf(out, "\"NOTES\","); - fprintf(out, "\"ANNIVERSARY\"\n"); + 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); @@ -1802,73 +2039,87 @@ palm_export_database(FILE *out, struct db_enumerator e) static int vcard_export_database(FILE *out, struct db_enumerator e) { - int j; + 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))); - db_enumerate_items(e) { - fprintf(out, "BEGIN:VCARD\r\nFN:%s\r\n", - safe_str(db_name_get(e.item))); - - name = get_surname(db_name_get(e.item)); - for( j = strlen(db_name_get(e.item)) - 1; j >= 0; j-- ) { - if((db_name_get(e.item))[j] == ' ') - break; - } - fprintf(out, "N:%s;%.*s\r\n", - safe_str(name), - j, - safe_str(db_name_get(e.item)) - ); - - free(name); - - if(db_fget(e.item, ADDRESS)) - fprintf(out, "ADR:;;%s;%s;%s;%s;%s;%s\r\n", - safe_str(db_fget(e.item, ADDRESS)), - safe_str(db_fget(e.item, ADDRESS2)), - safe_str(db_fget(e.item, CITY)), - safe_str(db_fget(e.item, STATE)), - safe_str(db_fget(e.item, ZIP)), - safe_str(db_fget(e.item, COUNTRY)) - ); - - if(db_fget(e.item, PHONE)) - fprintf(out, "TEL;HOME:%s\r\n", - db_fget(e.item, PHONE)); - if(db_fget(e.item, WORKPHONE)) - fprintf(out, "TEL;WORK:%s\r\n", - db_fget(e.item, WORKPHONE)); - if(db_fget(e.item, FAX)) - fprintf(out, "TEL;FAX:%s\r\n", - db_fget(e.item, FAX)); - if(db_fget(e.item, MOBILEPHONE)) - fprintf(out, "TEL;CELL:%s\r\n", - db_fget(e.item, MOBILEPHONE)); - - tmp = db_email_get(e.item); - if(*tmp) { - emails = csv_to_abook_list(tmp); - - for(em = emails; em; em = em->next) - fprintf(out, "EMAIL;INTERNET:%s\r\n", em->data); - - abook_list_free(&emails); - } - free(tmp); + 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(e.item, NOTES)) - fprintf(out, "NOTE:%s\r\n", - db_fget(e.item, NOTES)); - if(db_fget(e.item, URL)) - fprintf(out, "URL:%s\r\n", - db_fget(e.item, URL)); + 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); - fprintf(out, "END:VCARD\r\n\r\n"); + 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"); - return 0; } /* @@ -1898,16 +2149,51 @@ mutt_alias_genalias(int i) 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 = mutt_alias_genalias(e.item); + 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 */ @@ -1915,8 +2201,12 @@ mutt_alias_export(FILE *out, struct db_enumerator e) if (*email) { /* output first email address */ - fprintf(out, "alias %s %s <%s>\n", - alias, + 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); @@ -1934,20 +2224,53 @@ mutt_alias_export(FILE *out, struct db_enumerator e) while (email_addresses-- > 1) { roll_emails(e.item, ROTATE_RIGHT); get_first_email(email, e.item); - fprintf(out, "alias %s__%s %s <%s>\n", - alias, - email, + 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 */ @@ -2212,6 +2535,182 @@ bsdcal_export_database(FILE *out, struct db_enumerator e) 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); + while(*ft) 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 * sizeof(char*)); + + 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 */