/************************************************************************ * Copyright (C) 2000,2001 Thomas Schulz * * */ static char rcsid[] = "$Id: vcaconv.c,v 1.17 2001/02/17 20:56:49 tsch Rel $"; /* * * * vCard,vCalendar conversion * * * ************************************************************************* * * * ??? is "fixme" mark: sections of code needing fixes * * * * for usage run vcaconv -h or see function usage() below. * * * ************************************************************************/ #include /* printf(), .. */ #include /* strcasecmp(), .. */ #include /* getopt(), optarg, .. */ #include /* isdigit() */ #include /* struct tm, time() .. */ #include "vcc.h" /* VObject, .. */ #include "syntrans.h" /* FILEADDR, .. */ #include "dataio.h" /* ic35addr_to_vcard()..*/ #include "vcutil.h" /* vca_type(), .. */ #include "util.h" /* uchar, .. */ NOTUSED(rcsid) extern char * pkgvers; /* these are all */ extern char * pkgdate; /* in versinfo.c, which is */ extern char * bldinfo; /* auto-generated by Makefile */ /* get VObject's string value (dupStr()'d) * -------------------------- */ static char * dupStrValue( VObject * vobj, const char * id ) { char * strval; if ( (strval = dupStringValue( vobj, id )) == NULL ) strval = dupStr( "", 0 ); return strval; } /* ==================================================== */ /* parse VObject list from vCard/vCal file */ /* ==================================================== */ /* parse error handler * ------------------- */ static bool _vca_error; static char * _vca_fname; static void _vca_errmsg( char * msg ) /* mimeErrorHandler for Parse_MIME_FromFile() */ { error( "vcafile \"%s\": %s", _vca_fname, msg ); _vca_error = TRUE; } /* parse vCard,vCal file * --------------------- */ static VObject * vca_parse( char * fname ) { FILE * infp; VObject * vlist; if ( fname && *fname && strcmp( fname, "-" ) != 0 ) { if ( (infp = fopen( fname, "r" )) == NULL ) { error( "cannot open vcafile: %s", fname ); return NULL; } } else { fname = "(stdin)"; infp = stdin; } registerMimeErrorHandler( _vca_errmsg ); _vca_error = FALSE; _vca_fname = fname; vlist = Parse_MIME_FromFile( infp ); if ( _vca_error ) { cleanVObjects( vlist ); vlist = NULL; } if ( infp != stdin ) fclose( infp ); return vlist; } /* ==================================== */ /* sort list of VObjects */ /* ==================================== */ /* compare property of VObjects * ---------------------------- */ static int _compare_vprop( const char * id, VObject * vobj1, VObject * vobj2 ) { int cmp; if ( !( id && *id ) || (vobj1 == NULL && vobj2 == NULL) ) return 0; if ( vobj1 == NULL ) return -1; if ( vobj2 == NULL ) return +1; if ( strcasecmp( id, VCNameProp ) == 0 ) { VObject * vprop1 = isAPropertyOf( vobj1, id ); VObject * vprop2 = isAPropertyOf( vobj2, id ); if ( (cmp = _compare_vprop( VCFamilyNameProp, vprop1, vprop2 )) != 0 ) return cmp; else return _compare_vprop( VCGivenNameProp, vprop1, vprop2 ); } if ( strcasecmp( id, XPilotIdProp ) == 0 ) { ulong val1 = LongValue( vobj1, id ); ulong val2 = LongValue( vobj2, id ); if ( val1 < val2 ) return -1; if ( val1 > val2 ) return +1; return 0; } if ( strcasecmp( id, VCLastModifiedProp ) == 0 || strcasecmp( id, VCLastRevisedProp ) == 0 || strcasecmp( id, VCDTstartProp ) == 0 || strcasecmp( id, VCDTendProp ) == 0 ) { time_t time1 = isodtime_to_unixtime( vobj1, id ); time_t time2 = isodtime_to_unixtime( vobj2, id ); if ( time1 < time2 ) return -1; if ( time1 > time2 ) return +1; return 0; } { char * str1 = dupStrValue( vobj1, id ); char * str2 = dupStrValue( vobj2, id ); cmp = strcmp( str1, str2 ); deleteStr( str1 ); deleteStr( str2 ); return cmp; } } /* compare VObjects for sorting * ---------------------------- */ static int _compare_vobj( const void * pvobj1, const void * pvobj2 ) { VObject * vobj1 = *(VObject**)pvobj1; VObject * vobj2 = *(VObject**)pvobj2; int type1 = vca_type( vobj1 ); int type2 = vca_type( vobj2 ); int cmp; if ( type1 < type2 ) return -1; else if ( type1 > type2 ) return +1; switch ( type1 ) { case VCARD: /* addr: Name, REV, */ if ( (cmp = _compare_vprop( VCNameProp, vobj1, vobj2 )) != 0 ) return cmp; if ( (cmp = _compare_vprop( VCLastRevisedProp, vobj1, vobj2 )) != 0 ) return cmp; break; case VMEMO: /* memo: subject, */ if ( (cmp = _compare_vprop( VCSummaryProp, vobj1, vobj2 )) != 0 ) return cmp; if ( (cmp = _compare_vprop( VCLastModifiedProp, vobj1, vobj2 )) != 0 ) return cmp; break; case VEVENT: /* vcal.sched: DTSTART, subject, rev, */ if ( (cmp = _compare_vprop( VCDTstartProp, vobj1, vobj2 )) != 0 ) return cmp; if ( (cmp = _compare_vprop( VCSummaryProp, vobj1, vobj2 )) != 0 ) return cmp; if ( (cmp = _compare_vprop( VCLastModifiedProp, vobj1, vobj2 )) != 0 ) return cmp; break; case VTODO: /* vcal.todo: DTSTART, subject, rev, */ if ( (cmp = _compare_vprop( VCDueProp, vobj1, vobj2 )) != 0 ) return cmp; if ( (cmp = _compare_vprop( VCSummaryProp, vobj1, vobj2 )) != 0 ) return cmp; if ( (cmp = _compare_vprop( VCLastModifiedProp, vobj1, vobj2 )) != 0 ) return cmp; break; case VCAL: /* multiple vCAL ? VERSION, PRODID */ if ( (cmp = _compare_vprop( VCVersionProp, vobj1, vobj2 )) != 0 ) return cmp; return _compare_vprop( VCProdIdProp, vobj1, vobj2 ); } /* same with above compares: X-PILOTID, UID */ if ( (cmp = _compare_vprop( XPilotIdProp, vobj1, vobj2 )) != 0 ) return cmp; return _compare_vprop( VCUniqueStringProp, vobj1, vobj2 ); } /* sort VObject's property list * ---------------------------- */ struct propseq { int seq; char * id; }; static struct propseq _propseq[] = { { -6, VCNameProp }, { -5, VCDTstartProp }, { -4, VCDTendProp }, { -3, VCDueProp }, { -2, VCSummaryProp }, { -1, VCDescriptionProp }, { +1, VCCategoriesProp }, { +2, VCDCreatedProp }, { +3, VCLastModifiedProp }, { +4, VCLastRevisedProp }, { +5, VCUniqueStringProp }, { +6, XPilotIdProp }, { +7, XPilotStatusProp }, { 0, NULL } }; static int _propid_seq( const char * id ) { struct propseq * pseq; for ( pseq = _propseq; pseq->id; ++pseq ) if ( strcasecmp( id, pseq->id ) == 0 ) return pseq->seq; return 0; } static int /* compare properties for sorting */ _compare_props( const void * pvprop1, const void * pvprop2 ) { VObject * vprop1 = *(VObject**)pvprop1; VObject * vprop2 = *(VObject**)pvprop2; const char *id1, *id2; int seq1, seq2; char *str1, *str2; int cmp; seq1 = _propid_seq( id1 = vObjectName( vprop1 ) ); seq2 = _propid_seq( id2 = vObjectName( vprop2 ) ); if ( seq1 < seq2 ) return -1; if ( seq1 > seq2 ) return +1; if ( (cmp = strcasecmp( id1, id2 )) == 0 ) { cmp = strcmp( str1 = dupStrValue( vprop1, NULL ), str2 = dupStrValue( vprop2, NULL ) ); deleteStr( str1 ); deleteStr( str2 ); } return cmp; } static void /* sort VObject's property list */ vobjsort( VObject * vobj ) { size_t n, i; VObjectIterator iter; VObject ** vproptab; VObject * vprop; n = 0; /* count number of properties, alloc table */ initPropIterator( &iter, vobj ); while ( moreIteration( &iter ) ) { (void)nextVObject( &iter ); ++n; } vproptab = malloc ( n * sizeof(vproptab[0]) ); if ( vproptab == NULL ) return; /* build table of property ptrs and sort */ i = 0; initPropIterator( &iter, vobj ); while ( i < n && moreIteration( &iter ) ) vproptab[i++] = nextVObject( &iter ); qsort( vproptab, n, sizeof(vproptab[0]), _compare_props ); /* rebuild property list in sorted sequ */ for ( i = 0; i < n; ++i ) if ( (vprop = delProp( vobj, vproptab[i] )) != NULL ) addVObjectProp( vobj, vprop ); free( vproptab ); } /* sort VObject list * ----------------- */ static int vca_sort( VObject ** vlist ) { size_t n, i; VObject * vcal; VObject ** vobjtab; VObject * vobj; VObjectIterator iter; /* count number of VObjects, alloc table */ n = 0; vcal = NULL; for ( vobj = *vlist; vobj !=NULL; vobj = nextVObjectInList( vobj ) ) { ++n; if ( vca_type( vobj ) == VCAL ) { if ( vcal == NULL ) vcal = vobj; initPropIterator( &iter, vobj ); while ( moreIteration( &iter ) ) { (void)nextVObject( &iter ); ++n; } } } vobjtab = malloc ( n * sizeof(vobjtab[0]) ); if ( vobjtab == NULL ) return ERR; /* build table of VObject ptrs and sort */ i = 0; for ( vobj = *vlist; vobj !=NULL; vobj = nextVObjectInList( vobj ) ) { vobjtab[i++] = vobj; if ( vca_type( vobj ) == VCAL ) { initPropIterator( &iter, vobj ); while ( moreIteration( &iter ) ) vobjtab[i++] = nextVObject( &iter ); } } qsort( vobjtab, n, sizeof(vobjtab[0]), _compare_vobj ); /* rebuild VObject list in sorted sequ */ for ( i = 0; i < n; ++i ) { vobj = vobjtab[i]; switch ( vca_type( vobj ) ) { case VCARD: case VMEMO: vobjsort( vobj ); /* sort VObject's property list */ /* fall thru */ case VCAL: vobj = delList( vlist, vobj ); if ( vobj != NULL ) addList( vlist, vobj ); break; case VEVENT: case VTODO: if ( vcal == NULL ) break; vobjsort( vobj ); /* sort VObject's property list */ vobj = delProp( vcal, vobj ); if ( vobj != NULL ) addVObjectProp( vcal, vobj ); break; } } free( vobjtab ); return OK; } /* ==================================== */ /* create telbook for handy */ /* ==================================== */ static bool hasCategory( VObject * vobj, char * wanted ) { char * categories; char * category; if ( !( vobj && wanted && *wanted ) ) return FALSE; categories = dupStrValue( vobj, VCCategoriesProp ); for ( category = strtok( categories, ";" ); category; category = strtok( NULL, ";" ) ) if ( strcasecmp( category, wanted ) == 0 ) break; deleteStr( categories ); return (bool)( category != NULL ); } static char * NameValue( VObject * vobj, const char * type ) { VObject * vprop; char * namestr; if ( !( vobj && type && *type ) || (vprop = isAPropertyOf( vobj, VCNameProp )) == NULL ) return NULL; if ( (namestr = dupStrValue( vprop, type )) && *namestr ) return namestr; deleteStr( namestr ); if ( (namestr = dupStrValue( vprop, VCFamilyNameProp )) && *namestr ) return namestr; deleteStr( namestr ); return dupStrValue( vprop, VCGivenNameProp ); } static char * TelnoValue( VObject * vobj, const char * type ) { VObjectIterator iter, teliter; VObject * vprop; VObject * teltype; VObject * telpref; VObject * teldflt; char * telstr, *src, *dst; if ( !( vobj && type && *type ) ) return NULL; teltype = telpref = teldflt = NULL; initPropIterator( &iter, vobj ); while ( moreIteration( &iter ) ) { vprop = nextVObject( &iter ); if ( strcmp( vObjectName( vprop ), VCTelephoneProp ) != 0 ) continue; if ( teltype == NULL && isAPropertyOf( vprop, type ) ) teltype = vprop; if ( telpref == NULL && isAPropertyOf( vprop, VCPreferredProp ) ) telpref = vprop; initPropIterator( &teliter, vprop ); if ( teldflt == NULL && ! moreIteration( &teliter ) ) teldflt = vprop; } telstr = dupStrValue( teltype ? teltype : ( telpref ? telpref : teldflt ), NULL ); if ( (dst = src = telstr) && *telstr ) { do { if ( (src == telstr && *src == '+') /* international +49 */ || isdigit( *src ) ) /* skip separators, because */ *dst++ = *src; /* handy does not like them */ } while ( *src++ ); } return telstr; } /* * special categories for handy telbook: * S35SIM pre-installed entries from S35i * HANDY created for import into handy * special conversions * - vCards for handy have only VCNameProp.VCFamilyNameProp and * VCTelephoneProp with VCCellularProp attribute * for category "Business" and other derive from VCFamilyNameProp * for category "Personal" derive from VCGivenNameProp * - for multiple telnos make multiple vCards with append to name: * Business: append none, "-priv", "-mobil" for WORK, HOME, CELL * Personal: append "-Buero", none, "-mobil" for WORK, HOME, CELL * - for category "Personal" make "VIP" entries by appending "!" */ static VObject * new_handycard( char * name, char * suffix, char * telno ) { VObject * vcard; char namebuff[18+1]; time_t tnow; struct tm * ptm; char isodtime[24]; if ( !(name && *name) || !(telno && *telno) || (vcard = newVObject( VCCardProp )) == NULL ) return NULL; strncat( strcpy( namebuff, "" ), name, sizeof(namebuff)-1 ); if ( suffix && *suffix ) { namebuff[ sizeof(namebuff)-1 - 4 ] = '\0'; strncat( namebuff, suffix, sizeof(namebuff)-1 - strlen(namebuff) ); } addPropValue( vcard, VCFullNameProp, namebuff ); addPropValue( addProp( vcard, VCNameProp ), VCFamilyNameProp, namebuff ); addProp( addPropValue( vcard, VCTelephoneProp, telno ), VCCellularProp ); addPropValue( vcard, VCCategoriesProp, "HANDY" ); tnow = time( NULL ); ptm = localtime( &tnow ); strftime( isodtime, sizeof(isodtime), "%Y-%m-%dT%H:%M:%S", ptm ); addPropValue( vcard, VCLastRevisedProp, isodtime ); return vcard; } static int vcard_to_handy( VObject ** vlist ) { VObject * vobj; VObject * vcard; VObject * vdel; VObject * handylist; VObject * vnext; char * name, *tel1, *tel2; vdel = handylist = NULL; for ( vobj = *vlist; vobj !=NULL; vobj = nextVObjectInList( vobj ) ) { if ( vca_type( vobj ) != VCARD ) continue; if ( hasCategory( vobj, "HANDY" ) ) { if ( vdel ) cleanVObject( vdel ); vdel = delList( vlist, vobj ); continue; } if ( hasCategory( vobj, "NOTHANDY" ) ) continue; if ( hasCategory( vobj, "S35SIM" ) ) { name = NameValue( vobj, VCGivenNameProp ); if ( (tel1 = TelnoValue( vobj, VCCellularProp )) && *tel1 && (vcard = new_handycard( name, NULL, tel1 )) != NULL ) addList( &handylist, vcard ); deleteStr( tel1 ); deleteStr( name ); } else if ( hasCategory( vobj, "Personal" ) ) { name = NameValue( vobj, VCGivenNameProp ); if ( (tel1 = TelnoValue( vobj, VCHomeProp )) && *tel1 && (vcard = new_handycard( name, "!", tel1 )) != NULL ) addList( &handylist, vcard ); if ( (tel2 = TelnoValue( vobj, VCWorkProp )) && *tel2 && strcmp( tel1, tel2 ) != 0 && (vcard = new_handycard( name, "-Büro!", tel2 )) != NULL ) addList( &handylist, vcard ); deleteStr( tel2 ); if ( (tel2 = TelnoValue( vobj, VCCellularProp )) && *tel2 && strcmp( tel1, tel2 ) != 0 && (vcard = new_handycard( name, "-mobil!", tel2 )) != NULL ) addList( &handylist, vcard ); deleteStr( tel2 ); deleteStr( tel1 ); deleteStr( name ); } else { /* Business etc. */ name = NameValue( vobj, VCFamilyNameProp ); if ( (tel1 = TelnoValue( vobj, VCWorkProp )) && *tel1 && (vcard = new_handycard( name, NULL, tel1 )) != NULL ) addList( &handylist, vcard ); if ( (tel2 = TelnoValue( vobj, VCHomeProp )) && *tel2 && strcmp( tel1, tel2 ) != 0 && (vcard = new_handycard( name, "-priv", tel2 )) != NULL ) addList( &handylist, vcard ); deleteStr( tel2 ); if ( (tel2 = TelnoValue( vobj, VCCellularProp )) && *tel2 && strcmp( tel1, tel2 ) != 0 && (vcard = new_handycard( name, "-mobil", tel2 )) != NULL ) addList( &handylist, vcard ); deleteStr( tel2 ); deleteStr( tel1 ); deleteStr( name ); } } if ( vdel ) cleanVObject( vdel ); vobj = handylist; while ( vobj ) { vnext = nextVObjectInList( vobj ); addList( vlist, vobj ); vobj = vnext; } return OK; } /* -------------------- M A I N - program ----------------------------- */ static void versinfo( void ) { printf( "vCard/vCalendar converter %s (%s)\n", pkgvers, pkgdate ); printf( "%s\n", bldinfo ); printf( "Copyright (C) 2000,2001 Thomas Schulz\n" "This is free software; see the GNU General Public Licence version 2 in\n" "the file named COPYING for copying conditions. There is NO warranty.\n" ); } static void usage( void ) { static char * usetext[] = { "usage:", " vcaconv bin2vca binfile vcafile", " convert IC35 binary data to vCard,vCalendar format", " vcaconv bin2txt binfile txtfile", " convert IC35 binary data to plain text format", " vcaconv vca2bin vcafile binfile", " convert vCard,vCalendar file to IC35 binary data", " vcaconv prvca vcafile", " pretty print vCard/vCalendar format 'vcafile'", " vcaconv sortvca vcafile", " sort vCard,vCalendar file to standard output", " vcaconv imphandy vcafile", " create vCards in HANDY category for upload to mobile phone", " if inputfile or outputfile is absent or -, standard input", " or standard output will be used", "options:", " -V --version show version information and exit", " -h --help show this help and exit", NULL }; char ** lptr; for ( lptr = usetext; *lptr != NULL; ++lptr ) printf( "%s\n", *lptr ); } int main( int argc, char *argv[] ) { static struct option const long_opts[] = { { "help", no_argument, NULL, 'h' }, { "version", no_argument, NULL, 'V' }, { NULL, 0, NULL, 0 } }; for ( ; ; ) { switch ( getopt_long( argc, argv, "hV", long_opts, NULL ) ) { default: /* invalid option */ fprintf( stderr, "use 'vcaconv --help' for more information\n" ); return 1; case 'h': /* show Help and exit */ usage(); return 0; case 'V': /* show Version and exit */ versinfo(); return 0; case -1: /* end of options */ break; } break; } if ( argc < 2 ) { error( "missing conversion\n" "use 'vcaconv --help' for more information" ); return 1; } if ( ( strcmp( argv[1], "bin2vca" ) == 0 || strcmp( argv[1], "bin2txt" ) == 0 || strcmp( argv[1], "vca2bin" ) == 0 ) && 2 <= argc && argc <= 4 ) { void * rec; argv[1][3] = '\0'; /* split input,output format */ if ( pim_openinp( argv[1]+0, argc >= 3 ? argv[2] : "-" ) != OK ) { error( "bad input format/file: %s %s\n", argv[1]+0, argv[2] ); return 1; } if ( pim_openout( argv[1]+4, argc >= 4 ? argv[3] : "-" ) != OK ) { error( "bad output format/file: %s %s\n", argv[1]+4, argv[3] ); return 1; } while ( (rec = pim_getrec( FILE_ANY )) != NULL ) pim_putrec( rec ); pim_close(); } else if ( strcmp( argv[1], "prvca" ) == 0 && 2 <= argc && argc <= 3 ) { VObject * vlist; VObject * vdel; if ( (vlist = vca_parse( argc >= 3 ? argv[2] : "-" )) == NULL ) return 1; while ( vlist ) { printVObject( stdout, vlist ); vlist = nextVObjectInList( vdel = vlist ); cleanVObject( vdel ); } } else if ( strcmp( argv[1], "sortvca" ) == 0 && 2 <= argc && argc <= 3 ) { VObject * vlist; VObject * vdel; if ( (vlist = vca_parse( argc >= 3 ? argv[2] : "-" )) == NULL ) return 1; vca_sort( &vlist ); while ( vlist ) { writeVObject( stdout, vlist ); vlist = nextVObjectInList( vdel = vlist ); cleanVObject( vdel ); } } else if ( strcmp( argv[1], "imphandy" ) == 0 && 2 <= argc && argc <= 3 ) { VObject * vlist; VObject * vdel; if ( (vlist = vca_parse( argc >= 3 ? argv[2] : "-" )) == NULL ) return 1; vcard_to_handy( &vlist ); while ( vlist ) { writeVObject( stdout, vlist ); vlist = nextVObjectInList( vdel = vlist ); cleanVObject( vdel ); } } else { error( "unknown conversion: %s\n" "use 'vcaconv --help' for more information", argv[1] ); return 1; } return 0; }