ic35link/src/vcaconv.c

723 lines
20 KiB
C
Raw Blame History

/************************************************************************
* 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 <stdio.h> /* printf(), .. */
#include <string.h> /* strcasecmp(), .. */
#include <getopt.h> /* getopt(), optarg, .. */
#include <ctype.h> /* isdigit() */
#include <time.h> /* 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;
}