From d64e7cf12f73a73a0c5b99806fd695d428bea72f Mon Sep 17 00:00:00 2001 From: Damon Chaplin Date: Wed, 6 Feb 2002 23:16:12 +0000 Subject: major changes to support RRULE/EXRULE, VALARMS and several other 2002-02-06 Damon Chaplin * src/libicalvcal/icalvcal.c: major changes to support RRULE/EXRULE, VALARMS and several other properties. * src/libicalvcal/icalvcal.h (icalvcal_convert_with_defaults): new function to pass defaults for a few values into the importer. These are used when the vCalendar file doesn't provide the property but it is required in iCalendar. * src/libicalvcal/vcc.y: support multi-valued properties, by appending new ones and separating by ';'. This was pinched from our changes to evolution/libversit/vcc.y. * src/libicalvcal/Makefile.am: renamed library to libicalvcal-evolution Don't install the headers. * design-data/parameters.csv: added new error for error's parsing vCalendar properties. svn path=/trunk/; revision=15582 --- libical/ChangeLog | 20 + libical/design-data/parameters.csv | 2 +- libical/src/libicalvcal/Makefile.am | 25 +- libical/src/libicalvcal/icalvcal.c | 1269 ++++++++++++++++++++++++++++++++--- libical/src/libicalvcal/icalvcal.h | 15 + libical/src/libicalvcal/vcc.y | 24 +- 6 files changed, 1268 insertions(+), 87 deletions(-) (limited to 'libical') diff --git a/libical/ChangeLog b/libical/ChangeLog index 5d215cedfb..f4818cd132 100644 --- a/libical/ChangeLog +++ b/libical/ChangeLog @@ -1,3 +1,23 @@ +2002-02-06 Damon Chaplin + + * src/libicalvcal/icalvcal.c: major changes to support RRULE/EXRULE, + VALARMS and several other properties. + + * src/libicalvcal/icalvcal.h (icalvcal_convert_with_defaults): new + function to pass defaults for a few values into the importer. These + are used when the vCalendar file doesn't provide the property but it + is required in iCalendar. + + * src/libicalvcal/vcc.y: support multi-valued properties, by appending + new ones and separating by ';'. This was pinched from our changes to + evolution/libversit/vcc.y. + + * src/libicalvcal/Makefile.am: renamed library to libicalvcal-evolution + Don't install the headers. + + * design-data/parameters.csv: added new error for error's parsing + vCalendar properties. + 2002-01-28 Dan Winship * src/libical/icalrecur.c (icalrecur_add_bydayrules): Skip over diff --git a/libical/design-data/parameters.csv b/libical/design-data/parameters.csv index c71eb115eb..f223b415a8 100644 --- a/libical/design-data/parameters.csv +++ b/libical/design-data/parameters.csv @@ -20,5 +20,5 @@ "TZID","const char*", "VALUE","icalparameter_value","BINARY;BOOLEAN;DATE;DURATION;FLOAT;INTEGER;PERIOD;RECUR;TEXT;TIME;URI;ERROR;DATE-TIME;UTC-OFFSET;CAL-ADDRESS" "X","const char*", -"X-LIC-ERRORTYPE","icalparameter_xlicerrortype","COMPONENT-PARSE-ERROR;PROPERTY-PARSE-ERROR;PARAMETER-NAME-PARSE-ERROR;PARAMETER-VALUE-PARSE-ERROR;VALUE-PARSE-ERROR;INVALID-ITIP;UNKNOWN-VCAL-PROP-ERROR;MIME-PARSE-ERROR" +"X-LIC-ERRORTYPE","icalparameter_xlicerrortype","COMPONENT-PARSE-ERROR;PROPERTY-PARSE-ERROR;PARAMETER-NAME-PARSE-ERROR;PARAMETER-VALUE-PARSE-ERROR;VALUE-PARSE-ERROR;INVALID-ITIP;UNKNOWN-VCAL-PROP-ERROR;MIME-PARSE-ERROR;VCAL-PROP-PARSE-ERROR" "X-LIC-COMPARETYPE","icalparameter_xliccomparetype","EQUAL;NOTEQUAL;LESS;GREATER;LESSEQUAL;GREATEREQUAL;REGEX" diff --git a/libical/src/libicalvcal/Makefile.am b/libical/src/libicalvcal/Makefile.am index c409347d14..e8e8310941 100644 --- a/libical/src/libicalvcal/Makefile.am +++ b/libical/src/libicalvcal/Makefile.am @@ -1,4 +1,4 @@ -lib_LTLIBRARIES = libicalvcal.la +lib_LTLIBRARIES = libicalvcal-evolution.la INCLUDES = \ -I$(top_builddir) \ @@ -6,22 +6,27 @@ INCLUDES = \ -I$(top_builddir)/src/libical \ -I$(top_srcdir)/src/libicalss -libicalvcal_la_LDFLAGS = -version-info 0:0:0 +libicalvcal_evolution_la_LDFLAGS = -version-info 0:0:0 -libicalvcal_la_SOURCES = \ +libicalvcal_evolution_la_SOURCES = \ + icalvcal.h \ icalvcal.c \ + port.h \ + vcc.h \ vcc.y \ + vobject.h \ vobject.c \ + vcaltmp.h \ vcaltmp.c -libicalvcalincludedir = $(includedir)/libicalvcal +#libicalvcalincludedir = $(includedir)/evolution/libicalvcal -libicalvcalinclude_HEADERS = \ - icalvcal.h \ - port.h \ - vcc.h \ - vobject.h \ - vcaltmp.h +#libicalvcalinclude_HEADERS = \ +# icalvcal.h \ +# port.h \ +# vcc.h \ +# vobject.h \ +# vcaltmp.h EXTRA_DIST = \ README.TXT \ diff --git a/libical/src/libicalvcal/icalvcal.c b/libical/src/libicalvcal/icalvcal.c index 24c3155178..f070c9cd57 100644 --- a/libical/src/libicalvcal/icalvcal.c +++ b/libical/src/libicalvcal/icalvcal.c @@ -46,22 +46,113 @@ enum datatype { COMPONENT, PROPERTY, PARAMETER, - UNSUPPORTED + UNSUPPORTED, + IGNORE }; +/* The indices must match between the strings and the codes. */ +char *weekdays[] = { "SU", "MO", "TU", "WE", "TH", "FR", "SA" }; +int weekday_codes[] = { + ICAL_SUNDAY_WEEKDAY, + ICAL_MONDAY_WEEKDAY, + ICAL_TUESDAY_WEEKDAY, + ICAL_WEDNESDAY_WEEKDAY, + ICAL_THURSDAY_WEEKDAY, + ICAL_FRIDAY_WEEKDAY, + ICAL_SATURDAY_WEEKDAY +}; struct conversion_table_struct { char* vcalname; enum datatype type; - void* (*conversion_func)(int icaltype, VObject *o); + void* (*conversion_func)(int icaltype, VObject *o, icalcomponent *comp, + icalvcal_defaults *defaults); int icaltype; }; struct conversion_table_struct conversion_table[]; -void* dc_prop(int icaltype, VObject *object); +void* dc_prop(int icaltype, VObject *object, icalcomponent *comp, + icalvcal_defaults *defaults); + + + +/* Creates an error property with the given message. */ +static icalproperty* create_parse_error_property (const char *message, + const char *property_name, + const char *property_value) +{ + char temp[4096]; + icalparameter *error_param; + icalproperty *error_prop; + + snprintf (temp, 1024, "%s: %s:%s", message, property_name, property_value); + + error_param = icalparameter_new_xlicerrortype (ICAL_XLICERRORTYPE_VCALPROPPARSEERROR); + error_prop = icalproperty_new_xlicerror (temp); + icalproperty_add_parameter (error_prop, error_param); + + return error_prop; +} + + +char* get_string_value (VObject *object, int *free_string) +{ + switch (vObjectValueType(object)) { + case VCVT_USTRINGZ: + *free_string = 1; + return fakeCString(vObjectUStringZValue(object)); + + case VCVT_STRINGZ: + *free_string = 0; + return (char*) vObjectStringZValue(object); + } + + *free_string = 0; + + /* We return "" here, to cut down on the risk of crashing. */ + return ""; +} + + +static void convert_floating_time_to_utc (struct icaltimetype *itt) +{ + struct tm tmp_tm = { 0 }, *utc_tm; + time_t t; + + /* We assume the floating time is using the current Unix timezone. + So we convert to a time_t using mktime(), and then back to a struct tm + using gmtime, so it is the UTC time. */ + tmp_tm.tm_year = itt->year - 1900; + tmp_tm.tm_mon = itt->month - 1; + tmp_tm.tm_mday = itt->day; + tmp_tm.tm_hour = itt->hour; + tmp_tm.tm_min = itt->minute; + tmp_tm.tm_sec = itt->second; + tmp_tm.tm_isdst = -1; + + /* Convert to a time_t. */ + t = mktime (&tmp_tm); + + /* Now convert back to a struct tm, but with a UTC time. */ + utc_tm = gmtime (&t); + + /* Now put it back into the icaltime. */ + itt->year = utc_tm->tm_year + 1900; + itt->month = utc_tm->tm_mon + 1; + itt->day = utc_tm->tm_mday; + itt->hour = utc_tm->tm_hour; + itt->minute = utc_tm->tm_min; + itt->second = utc_tm->tm_sec; + + /* Set the is_utc flag. */ + itt->is_utc = 1; +} -static void icalvcal_traverse_objects(VObject *object,icalcomponent* last_comp, - icalproperty* last_prop) + +static void icalvcal_traverse_objects(VObject *object, + icalcomponent* last_comp, + icalproperty* last_prop, + icalvcal_defaults *defaults) { VObjectIterator iterator; char* name = "[No Name]"; @@ -89,7 +180,8 @@ static void icalvcal_traverse_objects(VObject *object,icalcomponent* last_comp, /* Handle X properties */ if(strncmp(name, "X-",2) == 0){ - icalproperty* prop = (icalproperty*)dc_prop(ICAL_X_PROPERTY,object); + icalproperty* prop = (icalproperty*)dc_prop(ICAL_X_PROPERTY,object, + last_comp, defaults); icalproperty_set_x_name(prop,name); icalcomponent_add_property(last_comp,prop); } else { @@ -110,11 +202,11 @@ static void icalvcal_traverse_objects(VObject *object,icalcomponent* last_comp, subc = (icalcomponent*)(conversion_table[i].conversion_func (conversion_table[i].icaltype, - object)); - - icalcomponent_add_component(last_comp,subc); + object, last_comp, defaults)); - assert(subc!=0); + if (subc) { + icalcomponent_add_component(last_comp,subc); + } break; } @@ -127,9 +219,11 @@ static void icalvcal_traverse_objects(VObject *object,icalcomponent* last_comp, icalproperty* prop = (icalproperty*)(conversion_table[i].conversion_func (conversion_table[i].icaltype, - object)); + object, last_comp, defaults)); - icalcomponent_add_property(last_comp,prop); + if (prop) + icalcomponent_add_property(last_comp,prop); + last_prop = prop; } @@ -163,6 +257,11 @@ static void icalvcal_traverse_objects(VObject *object,icalcomponent* last_comp, break; } + + case IGNORE: { + /* Do Nothing. */ + break; + } } } @@ -177,19 +276,21 @@ static void icalvcal_traverse_objects(VObject *object,icalcomponent* last_comp, should use it as the 'last_comp' */ if(subc!=0){ - icalvcal_traverse_objects(eachProp,subc,last_prop); + icalvcal_traverse_objects(eachProp,subc,last_prop,defaults); } else { - icalvcal_traverse_objects(eachProp,last_comp,last_prop); + icalvcal_traverse_objects(eachProp,last_comp,last_prop,defaults); } } } -icalcomponent* icalvcal_convert(VObject *object){ - +icalcomponent* icalvcal_convert_with_defaults (VObject *object, + icalvcal_defaults *defaults) +{ char* name = (char*)vObjectName(object); icalcomponent* container = icalcomponent_new(ICAL_XROOT_COMPONENT); icalcomponent* root; + icalproperty *prop; icalerror_check_arg_rz( (object!=0),"Object"); @@ -198,8 +299,15 @@ icalcomponent* icalvcal_convert(VObject *object){ return 0; /* HACK. Should return an error */ } +#if 0 + /* Just for testing. */ + printf ("This is the internal VObject representation:\n"); + printf ("===========================================\n"); + printVObject(stdout, object); + printf ("===========================================\n"); +#endif - icalvcal_traverse_objects(object,container,0); + icalvcal_traverse_objects(object,container,0,defaults); /* HACK. I am using the extra 'container' component because I am lazy. I know there is a way to get rid of it, but I did not care @@ -210,10 +318,25 @@ icalcomponent* icalvcal_convert(VObject *object){ icalcomponent_remove_component(container, root); icalcomponent_free(container); + /* We add a VERSION and PRODID here, to make it a valid iCalendar object, + but the application may change them if necessary. */ + prop = icalproperty_new_prodid ("-//Softwarestudio.org//" ICAL_PACKAGE + " version " ICAL_VERSION "//EN"); + icalcomponent_add_property (root, prop); + + prop = icalproperty_new_version ("2.0"); + icalcomponent_add_property (root, prop); + return root; } +icalcomponent* icalvcal_convert (VObject *object) +{ + return icalvcal_convert_with_defaults (object, NULL); +} + + /* comp() is useful for most components, but alarm, daylight and * timezone are different. In vcal, they are properties, and in ical, * they are components. Although because of the way that vcal treats @@ -221,7 +344,8 @@ icalcomponent* icalvcal_convert(VObject *object){ * timezone_comp() may not really be necessary, I think it would be * easier to use them. */ -void* comp(int icaltype, VObject *o) +void* comp(int icaltype, VObject *o, icalcomponent *comp, + icalvcal_defaults *defaults) { icalcomponent_kind kind = (icalcomponent_kind)icaltype; @@ -230,80 +354,1051 @@ void* comp(int icaltype, VObject *o) return (void* )c; } -void* alarm_comp(int icaltype, VObject *o) +/* vCalendar has 4 properties for alarms: AALARM, DALARM, MALARM, PALARM + (for audio, display, mail, and procedure alarms). + + AALARM has Run Time, Snooze Time, Repeat Count, Audio Content. + It may also have a TYPE parameter specifying the MIME type, e.g. + AALARM;TYPE=WAVE;VALUE=URL:19960415T235959; ; ; file:///mmedia/taps.wav + AALARM;TYPE=WAVE;VALUE=CONTENT-ID:19960903T060000;PT15M;4; + + DALARM has Run Time, Snooze Time, Repeat Count, Display String. + DALARM:19960415T235000;PT5M;2;Your Taxes Are Due !!! + + MALARM has Run Time, Snooze Time, Repeat Count, Email Address, Note. + MALARM:19960416T000000;PT1H;24;IRS@us.gov;The Check Is In The Mail! + + PALARM has Run Time, Snooze Time, Repeat Count, Procedure Name. + PALARM;VALUE=URL:19960415T235000;PT5M;2;file:///myapps/shockme.exe + + AALARM and PALARM: Check the VALUE is a URL. We won't support CONTENT-ID. + + + iCalendar uses one component, VALARM, for all of these, and uses an ACTION + property of "AUDIO", "DISPLAY", "EMAIL" or "PROCEDURE". + + The Run Time value can be copied into the iCalendar TRIGGER property, + except it must be UTC in iCalendar. If it is floating, we'll convert to + UTC using the current Unix timezone. + + The Snooze Time becomes DURATION, and the Repeat Count becomes REPEAT. + + For AALARM, the Audio Content becomes the ATTACH property, and the TYPE + becomes a FMTTYPE of this property (PCM -> 'audio/basic' (?), + WAVE -> 'audio/x-wav', AIFF -> 'audio/x-aiff'), e.g. + ATTACH;FMTTYPE=audio/basic:ftp://host.com/pub/sounds/bell-01.aud + + For DALARM, Display String becomes the DESCRIPTION property. + + For MALARM, Email Address becomes an ATTENDEE property, e.g. + ATTENDEE:MAILTO:john_doe@host.com + + For PALARM, the Procedure Name becomes an ATTACH property, like AALARM, e.g. + ATTACH;FMTTYPE=application/binary:ftp://host.com/novo-procs/felizano.exe +*/ + +/* This converts the vCalendar alarm properties into iCalendar properties and + adds them to the component. It returns 1 if the alarm is valid, 0 if not. */ +static int get_alarm_properties (icalcomponent *comp, VObject *object, + int icaltype, icalvcal_defaults *defaults) { - icalcomponent_kind kind = (icalcomponent_kind)icaltype; + VObjectIterator iterator; + icalproperty *trigger_prop = NULL, *duration_prop = NULL; + icalproperty *repeat_prop = NULL, *attach_prop = NULL; + icalproperty *summary_prop = NULL, *description_prop = NULL; + icalproperty *action_prop, *attendee_prop = NULL; + icalparameter *fmttype_param = NULL; + enum icalproperty_action action; + int value_is_url = 0, is_valid_alarm = 1; + + initPropIterator (&iterator, object); + while (moreIteration (&iterator)) { + VObject *eachProp = nextVObject (&iterator); + const char *name = vObjectName (eachProp); + char *s; + int free_string; + + s = get_string_value (eachProp, &free_string); + + /* Common properties. */ + if (!strcmp (name, VCRunTimeProp)) { + if (*s) { + struct icaltriggertype t; + icalparameter *param; + + /* Convert it to an icaltimetype. */ + t.time = icaltime_from_string (s); + + /* If it is a floating time, convert it to a UTC time. */ + if (!t.time.is_utc) + convert_floating_time_to_utc (&t.time); + + /* Create a TRIGGER property. */ + trigger_prop = icalproperty_new_trigger (t); + + /* vCalendar triggers are always specific DATE-TIME values. */ + param = icalparameter_new_value (ICAL_VALUE_DATETIME); + icalproperty_add_parameter (trigger_prop, param); + + icalcomponent_add_property (comp, trigger_prop); + } - icalcomponent* c = icalcomponent_new(kind); + } else if (!strcmp (name, VCSnoozeTimeProp)) { + struct icaldurationtype d; + + /* Parse the duration string. + FIXME: vCalendar also permits 'Y' (Year) and 'M' (Month) here, + which we don't handle at present. Though it is unlikely they + will be used as a snooze time between repeated alarms! */ + d = icaldurationtype_from_string (s); + + duration_prop = icalproperty_new_duration (d); + icalcomponent_add_property (comp, duration_prop); + + } else if (!strcmp (name, VCRepeatCountProp)) { + /* If it starts with a digit convert it into a REPEAT property. */ + if (*s && *s >= '0' && *s <= '9') { + repeat_prop = icalproperty_new_repeat (atoi (s)); + icalcomponent_add_property (comp, repeat_prop); + } + + } else if (!strcmp (name, VCValueProp)) { + /* We just remember if the value is a URL. */ + if (!strcmp (s, "URL")) { + value_is_url = 1; + } + + /* Audio properties && Procedure properties. */ + } else if (!strcmp (name, VCAudioContentProp) + || !strcmp (name, VCProcedureNameProp)) { + if (*s && !attach_prop) { + icalattach *attach; + + attach = icalattach_new_from_url (s); + attach_prop = icalproperty_new_attach (attach); + icalcomponent_add_property (comp, attach_prop); + + /* We output a "application/binary" FMTTYPE for Procedure + alarms. */ + if (!strcmp (name, VCProcedureNameProp) && !fmttype_param) + fmttype_param = icalparameter_new_fmttype ("application/binary"); + } + + } else if (!strcmp (name, "TYPE")) { + char *fmttype = NULL; + + if (!strcmp (s, "PCM")) + fmttype = "audio/basic"; + else if (!strcmp (s, "AIFF")) + fmttype = "audio/x-aiff"; + else if (!strcmp (s, "WAVE")) + fmttype = "audio/x-wav"; + + if (fmttype) + fmttype_param = icalparameter_new_fmttype (fmttype); + + /* Display properties. */ + } else if (!strcmp (name, VCDisplayStringProp)) { + if (!description_prop) { + description_prop = icalproperty_new_description (s); + icalcomponent_add_property (comp, description_prop); + } + + /* Mail properties. */ + } else if (!strcmp (name, VCEmailAddressProp)) { + if (*s && strlen (s) < 1000) { + char buffer[1024]; + + /* We need to add 'MAILTO:' before the email address, to make + it valid iCalendar. */ + sprintf (buffer, "MAILTO:%s", s); + attendee_prop = icalproperty_new_attendee (buffer); + icalcomponent_add_property (comp, attendee_prop); + } + + } else if (!strcmp (name, VCNoteProp)) { + if (!description_prop) { + description_prop = icalproperty_new_description (s); + icalcomponent_add_property (comp, description_prop); + } + + /* We also copy the Note to the SUMMARY property, since that is + required in iCalendar. */ + if (!summary_prop) { + summary_prop = icalproperty_new_summary (s); + icalcomponent_add_property (comp, summary_prop); + } + } + + if (free_string) + deleteStr (s); + } + + /* Add the FMTTYPE parameter to the ATTACH property if it exists. */ + if (fmttype_param) { + if (attach_prop) { + icalproperty_add_parameter (attach_prop, fmttype_param); + } else { + icalparameter_free (fmttype_param); + } + } + + + /* Now check if the alarm is valid, i.e. it has the required properties + according to its type. */ - return (void*)c; + /* All alarms must have a trigger. */ + if (!trigger_prop) + is_valid_alarm = 0; + + /* If there is a Duration but not a Repeat Count, we just remove the + Duration so the alarm only occurs once. */ + if (duration_prop && !repeat_prop) { + icalcomponent_remove_property (comp, duration_prop); + icalproperty_free (duration_prop); + duration_prop = NULL; + } + + /* Similarly if we have a Repeat Count but no Duration, we remove it. */ + if (repeat_prop && !duration_prop) { + icalcomponent_remove_property (comp, repeat_prop); + icalproperty_free (repeat_prop); + repeat_prop = NULL; + } + + switch (icaltype) { + case ICAL_XAUDIOALARM_COMPONENT: + action = ICAL_ACTION_AUDIO; + + /* Audio alarms must have an ATTACH property, which is a URL. + If they don't have one, we use the default alarm URL. */ + if (!attach_prop || !value_is_url) { + if (defaults && defaults->alarm_audio_url + && defaults->alarm_audio_fmttype) { + icalattach *attach; + + if (attach_prop) { + icalcomponent_remove_property (comp, attach_prop); + icalproperty_free (attach_prop); + } + + attach = icalattach_new_from_url (defaults->alarm_audio_url); + attach_prop = icalproperty_new_attach (attach); + icalcomponent_add_property (comp, attach_prop); + + fmttype_param = icalparameter_new_fmttype (defaults->alarm_audio_fmttype); + icalproperty_add_parameter (attach_prop, fmttype_param); + } else { + is_valid_alarm = 0; + } + } + break; + + case ICAL_XDISPLAYALARM_COMPONENT: + action = ICAL_ACTION_DISPLAY; + + /* Display alarms must have a DESCRIPTION. */ + if (!description_prop) { + if (defaults && defaults->alarm_description) { + description_prop = icalproperty_new_description (defaults->alarm_description); + icalcomponent_add_property (comp, description_prop); + } else { + is_valid_alarm = 0; + } + } + break; + + case ICAL_XEMAILALARM_COMPONENT: + action = ICAL_ACTION_EMAIL; + + /* Email alarms must have a SUMMARY, a DESCRIPTION, and an ATTENDEE. */ + if (!attendee_prop) { + is_valid_alarm = 0; + } else if (!summary_prop || !description_prop) { + if (!summary_prop && defaults->alarm_description) { + summary_prop = icalproperty_new_summary (defaults->alarm_description); + icalcomponent_add_property (comp, summary_prop); + } + + if (!description_prop && defaults->alarm_description) { + description_prop = icalproperty_new_description (defaults->alarm_description); + icalcomponent_add_property (comp, description_prop); + } + + if (!summary_prop || !description_prop) + is_valid_alarm = 0; + } + break; + + case ICAL_XPROCEDUREALARM_COMPONENT: + action = ICAL_ACTION_PROCEDURE; + + /* Procedure alarms must have an ATTACH property, which is a URL. + We don't support inline data. */ + if (!attach_prop) { + is_valid_alarm = 0; + } else if (!value_is_url) { + icalattach *attach; + const char *url; + + attach = icalproperty_get_attach (attach_prop); + url = icalattach_get_url (attach); + + /* Check for Gnome Calendar, which will just save a pathname. */ + if (url && url[0] == '/') { + int len; + char *new_url; + icalattach *new_attach; + + /* Turn it into a proper file: URL. */ + len = strlen (url) + 12; + + new_url = malloc (len); + strcpy (new_url, "file://"); + strcat (new_url, url); + + new_attach = icalattach_new_from_url (new_url); + free (new_url); + + icalproperty_set_attach (attach_prop, new_attach); + + } else { + is_valid_alarm = 0; + } + } + break; + + default: + /* Shouldn't reach here ever. */ + assert(0); + break; + } + + action_prop = icalproperty_new_action (action); + icalcomponent_add_property (comp, action_prop); + + return is_valid_alarm; } -void* daylight_comp(int icaltype, VObject *o) + +void* alarm_comp(int icaltype, VObject *o, icalcomponent *comp, + icalvcal_defaults *defaults) { icalcomponent_kind kind = (icalcomponent_kind)icaltype; + int is_valid_alarm; - icalcomponent* c = icalcomponent_new(kind); - - return (void*)c; + icalcomponent* c = icalcomponent_new(ICAL_VALARM_COMPONENT); + + is_valid_alarm = get_alarm_properties (c, o, icaltype, defaults); + + if (is_valid_alarm) { + return (void*)c; + } else { + icalcomponent_free (c); + return NULL; + } } -void* timezone_comp(int icaltype, VObject *o) +/* These #defines indicate conversion routines that are not defined yet. */ + +#define parameter 0 +#define rsvp_parameter 0 + + +void* transp_prop(int icaltype, VObject *object, icalcomponent *comp, + icalvcal_defaults *defaults) { - icalcomponent_kind kind = (icalcomponent_kind)icaltype; + icalproperty *prop = NULL; + char *s; + int free_string; - icalcomponent* c = icalcomponent_new(kind); + s = get_string_value (object, &free_string); - return (void*)c; + /* In vCalendar "0" means opaque, "1" means transparent, and >1 is + implementation-specific. So we just check for "1" and output + TRANSPARENT. For anything else, the default OPAQUE will be used. */ + if (!strcmp (s, "1")) { + prop = icalproperty_new_transp ("TRANSPARENT"); + } + + if (free_string) + deleteStr (s); + + return (void*)prop; } -/* These #defines indicate conversion routines that are not defined yet. */ +void* sequence_prop(int icaltype, VObject *object, icalcomponent *comp, + icalvcal_defaults *defaults) +{ + icalproperty *prop = NULL; + char *s; + int free_string, sequence; -#define categories_prop 0 -#define transp_prop 0 -#define status_prop 0 + s = get_string_value (object, &free_string); -#define parameter 0 -#define rsvp_parameter 0 + /* GnomeCalendar outputs '-1' for this. I have no idea why. + So we just check it is a valid +ve integer, and output 0 if it isn't. */ + sequence = atoi (s); + if (sequence < 0) + sequence = 0; + prop = icalproperty_new_sequence (sequence); + if (free_string) + deleteStr (s); + + return (void*)prop; +} -/* directly convertable property. The string representation of vcal is - the same as ical */ -void* dc_prop(int icaltype, VObject *object) +/* This handles properties which have multiple values, which are separated by + ';' in vCalendar but ',' in iCalendar. So we just switch those. */ +void* multivalued_prop(int icaltype, VObject *object, icalcomponent *comp, + icalvcal_defaults *defaults) +{ + icalproperty_kind kind = (icalproperty_kind)icaltype; + icalproperty *prop = NULL; + icalvalue *value; + icalvalue_kind value_kind; + char *s, *tmp_copy, *p; + int free_string; + + s = get_string_value (object, &free_string); + + tmp_copy = strdup (s); + + if (free_string) + deleteStr (s); + + if (tmp_copy) { + prop = icalproperty_new(kind); + + value_kind = icalenum_property_kind_to_value_kind (icalproperty_isa (prop)); + + for (p = tmp_copy; *p; p++) { + if (*p == ';') + *p = ','; + } + + value = icalvalue_new_from_string (value_kind, tmp_copy); + icalproperty_set_value (prop, value); + + free (tmp_copy); + } + + return (void*)prop; +} + + +void* status_prop(int icaltype, VObject *object, icalcomponent *comp, + icalvcal_defaults *defaults) +{ + icalproperty *prop = NULL; + char *s; + int free_string; + icalcomponent_kind kind; + + kind = icalcomponent_isa (comp); + + s = get_string_value (object, &free_string); + + /* In vCalendar: + VEVENT can have: "NEEDS ACTION" (default), "SENT", "TENTATIVE", + "CONFIRMED", "DECLINED", "DELEGATED". + VTODO can have: "ACCEPTED", "NEEDS ACTION" (default), "SENT", + "DECLINED", "COMPLETED", "DELEGATED". + (Those are the only 2 components - there is no VJOURNAL) + + In iCalendar: + VEVENT can have: "TENTATIVE", "CONFIRMED", "CANCELLED". + VTODO can have: "NEEDS-ACTION", "COMPLETED", "IN-PROCESS", "CANCELLED". + + So for VEVENT if it is "TENTATIVE" or "CONFIRMED" we keep it, otherwise + we skip it. + + For a VTODO if it is "NEEDS ACTION" we convert to "NEEDS-ACTION", if it + is "COMPLETED" we keep it, otherwise we skip it. + */ + if (kind == ICAL_VEVENT_COMPONENT) { + if (!strcmp (s, "TENTATIVE")) + prop = icalproperty_new_status (ICAL_STATUS_TENTATIVE); + else if (!strcmp (s, "CONFIRMED")) + prop = icalproperty_new_status (ICAL_STATUS_CONFIRMED); + + } else if (kind == ICAL_VTODO_COMPONENT) { + if (!strcmp (s, "NEEDS ACTION")) + prop = icalproperty_new_status (ICAL_STATUS_NEEDSACTION); + else if (!strcmp (s, "COMPLETED")) + prop = icalproperty_new_status (ICAL_STATUS_COMPLETED); + + } + + if (free_string) + deleteStr (s); + + return (void*)prop; +} + + +void* utc_datetime_prop(int icaltype, VObject *object, icalcomponent *comp, + icalvcal_defaults *defaults) { icalproperty_kind kind = (icalproperty_kind)icaltype; icalproperty *prop; icalvalue *value; icalvalue_kind value_kind; - char *s,*t=0; + char *s; + int free_string; + struct icaltimetype itt; prop = icalproperty_new(kind); - value_kind = - icalenum_property_kind_to_value_kind( - icalproperty_isa(prop)); + value_kind = icalenum_property_kind_to_value_kind (icalproperty_isa(prop)); + s = get_string_value (object, &free_string); - switch (vObjectValueType(object)) { - case VCVT_USTRINGZ: { - s = t = fakeCString(vObjectUStringZValue(object)); + /* Convert it to an icaltimetype. */ + itt = icaltime_from_string (s); + + /* If it is a floating time, convert it to a UTC time. */ + if (!itt.is_utc) + convert_floating_time_to_utc (&itt); + + value = icalvalue_new_datetime (itt); + icalproperty_set_value(prop,value); + + if (free_string) + deleteStr (s); + + return (void*)prop; +} + + +/* Parse the interval from the RRULE, returning a pointer to the first char + after the interval and any whitespace. s points to the start of the + interval. error_message is set if an error occurs. */ +static char* rrule_parse_interval (char *s, struct icalrecurrencetype *recur, + char **error_message) +{ + int interval = 0; + + /* It must start with a digit. */ + if (*s < '0' || *s > '9') { + *error_message = "Invalid Interval"; + return NULL; + } + + while (*s >= '0' && *s <= '9') + interval = (interval * 10) + (*s++ - '0'); + + /* It must be followed by whitespace. I'm not sure if anything else is + allowed. */ + if (*s != ' ' && *s != '\t') { + *error_message = "Invalid Interval"; + return NULL; + } + + /* Skip any whitespace. */ + while (*s == ' ' || *s == '\t') + s++; + + recur->interval = interval; + return s; +} + + +/* Parse the duration from the RRULE, either a COUNT, e.g. '#5', or an UNTIL + date, e.g. 20020124T000000. error_message is set if an error occurs. + If no duration is given, '#2' is assumed. */ +static char* rrule_parse_duration (char *s, struct icalrecurrencetype *recur, + char **error_message) +{ + /* If we've already found an error, just return. */ + if (*error_message) + return NULL; + + if (!s || *s == '\0') { + /* If we are at the end of the string, assume '#2'. */ + recur->count = 2; + + } else if (*s == '#') { + /* If it starts with a '#' it is the COUNT. Note that in vCalendar + #0 means forever, and setting recur->count to 0 means the same. */ + int count = 0; + + s++; + while (*s >= '0' && *s <= '9') + count = (count * 10) + (*s++ - '0'); + + recur->count = count; + + } else if (*s >= '0' && *s <= '9') { + /* If it starts with a digit it must be the UNTIL date. */ + char *e, buffer[20]; + int len; + + /* Find the end of the date. */ + e = s; + while ((*e >= '0' && *e <= '9') || *e == 'T' || *e == 'Z') + e++; + + /* Check it is a suitable length. */ + len = e - s; + if (len != 8 && len != 15 && len != 16) { + *error_message = "Invalid End Date"; + return NULL; + } + + /* Copy the date to our buffer and null-terminate it. */ + strncpy (buffer, s, len); + buffer[len] = '\0'; + + /* Parse it into the until field. */ + recur->until = icaltime_from_string (buffer); + + /* In iCalendar UNTIL must be UTC if it is a DATE-TIME. But we + don't really know what timezone the vCalendar times are in. So if + it can be converted to a DATE value, we do that. Otherwise we just + use the current Unix timezone. Should be OK 99% of the time. */ + if (!recur->until.is_utc) { + if (recur->until.hour == 0 && recur->until.minute == 0 + && recur->until.second == 0) + recur->until.is_date = 1; + else + convert_floating_time_to_utc (&recur->until); + } + + s = e; + + } else { + *error_message = "Invalid Duration"; + return NULL; + } + + + /* It must be followed by whitespace or the end of the string. + I'm not sure if anything else is allowed. */ + if (*s != '\0' && *s != ' ' && *s != '\t') { + *error_message = "Invalid Duration"; + return NULL; + } + + return s; +} + + +static char* rrule_parse_weekly_days (char *s, + struct icalrecurrencetype *recur, + char **error_message) +{ + int i; + + /* If we've already found an error, just return. */ + if (*error_message) + return NULL; + + for (i = 0; i < ICAL_BY_DAY_SIZE; i++) { + char *e; + int found_day, day; + + found_day = -1; + for (day = 0; day < 7; day++) { + if (!strncmp (weekdays[day], s, 2)) { + /* Check the next char is whitespace or the end of string. */ + e = s + 2; + if (*e == ' ' || *e == '\t' || *e == '\0') { + found_day = day; + break; + } + } + } + + if (found_day == -1) + break; + + recur->by_day[i] = weekday_codes[day]; + + s = e; + /* Skip any whitespace. */ + while (*s == ' ' || *s == '\t') + s++; + } + + /* Terminate the array, if it isn't full. */ + if (i < ICAL_BY_DAY_SIZE) + recur->by_day[i] = ICAL_RECURRENCE_ARRAY_MAX; + + return s; +} + + +static char* rrule_parse_monthly_days (char *s, + struct icalrecurrencetype *recur, + char **error_message) +{ + int i; + + /* If we've already found an error, just return. */ + if (*error_message) + return NULL; + + for (i = 0; i < ICAL_BY_MONTHDAY_SIZE; i++) { + char *e; + int month_day; + + if (!strncmp (s, "LD", 2)) { + month_day = -1; + e = s + 2; + } else { + month_day = strtol (s, &e, 10); + + /* Check we got a valid day. */ + if (month_day < 1 || month_day > 31) + break; + + /* See if it is followed by a '+' or '-'. */ + if (*e == '+') { + e++; + } else if (*e == '-') { + e++; + month_day = -month_day; + } + } + + /* Check the next char is whitespace or the end of the string. */ + if (*e != ' ' && *e != '\t' && *e != '\0') break; + + recur->by_month_day[i] = month_day; + + s = e; + /* Skip any whitespace. */ + while (*s == ' ' || *s == '\t') + s++; + } + + /* Terminate the array, if it isn't full. */ + if (i < ICAL_BY_MONTHDAY_SIZE) + recur->by_month_day[i] = ICAL_RECURRENCE_ARRAY_MAX; + + return s; +} + + +static char* rrule_parse_monthly_positions (char *s, + struct icalrecurrencetype *recur, + char **error_message) +{ + int occurrences[ICAL_BY_DAY_SIZE]; + int found_weekdays[7] = { 0 }; + int i, num_positions, elems, month_position, day; + int num_weekdays, only_weekday; + char *e; + + /* If we've already found an error, just return. */ + if (*error_message) + return NULL; + + /* First read the month position into our local occurrences array. */ + for (i = 0; i < ICAL_BY_DAY_SIZE; i++) { + int month_position; + + /* Check we got a valid position number. */ + month_position = *s - '0'; + if (month_position < 0 || month_position > 5) + break; + + /* See if it is followed by a '+' or '-'. */ + e = s + 1; + if (*e == '+') { + e++; + } else if (*e == '-') { + e++; + month_position = -month_position; } - case VCVT_STRINGZ: { - s = (char*)vObjectStringZValue(object); + + /* Check the next char is whitespace or the end of the string. */ + if (*e != ' ' && *e != '\t' && *e != '\0') break; + + occurrences[i] = month_position; + + s = e; + /* Skip any whitespace. */ + while (*s == ' ' || *s == '\t') + s++; + } + num_positions = i; + + /* Now read the weekdays in. */ + for (;;) { + char *e; + int found_day, day; + + found_day = -1; + for (day = 0; day < 7; day++) { + if (!strncmp (weekdays[day], s, 2)) { + /* Check the next char is whitespace or the end of string. */ + e = s + 2; + if (*e == ' ' || *e == '\t' || *e == '\0') { + found_day = day; + break; + } + } + } + + if (found_day == -1) + break; + + found_weekdays[found_day] = 1; + + s = e; + /* Skip any whitespace. */ + while (*s == ' ' || *s == '\t') + s++; + } + + /* Now merge them together into the recur->by_day array. If there is a + single position & weekday we output something like + 'BYDAY=TU;BYSETPOS=2', so Outlook will understand it. */ + num_weekdays = 0; + for (day = 0; day < 7; day++) { + if (found_weekdays[day]) { + num_weekdays++; + only_weekday = day; } } + if (num_positions == 1 && num_weekdays == 1) { + recur->by_day[0] = weekday_codes[only_weekday]; + recur->by_day[1] = ICAL_RECURRENCE_ARRAY_MAX; + + recur->by_set_pos[0] = occurrences[0]; + recur->by_set_pos[1] = ICAL_RECURRENCE_ARRAY_MAX; + } else { + elems = 0; + for (i = 0; i < num_positions; i++) { + month_position = occurrences[i]; + + for (day = 0; day < 7; day++) { + if (found_weekdays[day]) { + recur->by_day[elems] = (abs (month_position) * 8 + weekday_codes[day]) * ((month_position < 0) ? -1 : 1); + elems++; + if (elems == ICAL_BY_DAY_SIZE) + break; + } + } + + if (elems == ICAL_BY_DAY_SIZE) + break; + } + + /* Terminate the array, if it isn't full. */ + if (elems < ICAL_BY_DAY_SIZE) + recur->by_day[elems] = ICAL_RECURRENCE_ARRAY_MAX; + } + + return s; +} + + +static char* rrule_parse_yearly_months (char *s, + struct icalrecurrencetype *recur, + char **error_message) +{ + int i; + + /* If we've already found an error, just return. */ + if (*error_message) + return NULL; + + for (i = 0; i < ICAL_BY_MONTH_SIZE; i++) { + char *e; + int month; + + month = strtol (s, &e, 10); + + /* Check we got a valid month. */ + if (month < 1 || month > 12) + break; + + /* Check the next char is whitespace or the end of the string. */ + if (*e != ' ' && *e != '\t' && *e != '\0') + break; + + recur->by_month[i] = month; + + s = e; + /* Skip any whitespace. */ + while (*s == ' ' || *s == '\t') + s++; + } + + /* Terminate the array, if it isn't full. */ + if (i < ICAL_BY_MONTH_SIZE) + recur->by_month[i] = ICAL_RECURRENCE_ARRAY_MAX; + + return s; +} + + +static char* rrule_parse_yearly_days (char *s, + struct icalrecurrencetype *recur, + char **error_message) +{ + int i; + + /* If we've already found an error, just return. */ + if (*error_message) + return NULL; + + for (i = 0; i < ICAL_BY_YEARDAY_SIZE; i++) { + char *e; + int year_day; + + year_day = strtol (s, &e, 10); + + /* Check we got a valid year_day. */ + if (year_day < 1 || year_day > 366) + break; + + /* Check the next char is whitespace or the end of the string. */ + if (*e != ' ' && *e != '\t' && *e != '\0') + break; + + recur->by_year_day[i] = year_day; + + s = e; + /* Skip any whitespace. */ + while (*s == ' ' || *s == '\t') + s++; + } + + /* Terminate the array, if it isn't full. */ + if (i < ICAL_BY_YEARDAY_SIZE) + recur->by_year_day[i] = ICAL_RECURRENCE_ARRAY_MAX; + + return s; +} + - value = icalvalue_new_from_string(value_kind,s); - if(t!=0){ - deleteStr(t); + +/* Converts an RRULE/EXRULE property. + NOTE: There are a few things that this doesn't handle: + 1) vCalendar RRULE properties can contain an UNTIL date and a COUNT, and + the first to occur specifies the end of the recurrence. However they + are mutually exclusive in iCalendar. For now we just use the COUNT. + 2) For MONTHLY By Position recurrences, if no modifiers are given they + are to be calculated based on the DTSTART, e.g. if DTSTART is on the + 3rd Wednesday of the month then all occurrences are on the 3rd Wed. + This is awkward to do as we need to access the DTSTART property, which + may be after the RRULE property. So we don't do this at present. + 3) The Extended Recurrence Rule Grammar - we only support the Basic rules. + The extended grammar supports rules embedded in other rules, MINUTELY + recurrences, time modifiers in DAILY rules and maybe other stuff. +*/ + +void* rule_prop(int icaltype, VObject *object, icalcomponent *comp, + icalvcal_defaults *defaults) +{ + icalproperty_kind kind = (icalproperty_kind)icaltype; + icalproperty *prop = NULL; + icalvalue *value; + icalvalue_kind value_kind; + char *s, *p, *error_message = NULL; + const char *property_name; + int free_string; + struct icalrecurrencetype recur; + + s = get_string_value (object, &free_string); + + property_name = vObjectName (object); + + icalrecurrencetype_clear (&recur); + + if (*s == 'D') { + /* The DAILY RRULE only has an interval and duration (COUNT/UNTIL). */ + recur.freq = ICAL_DAILY_RECURRENCE; + p = rrule_parse_interval (s + 1, &recur, &error_message); + p = rrule_parse_duration (p, &recur, &error_message); + } else if (*s == 'W') { + /* The WEEKLY RRULE has weekday modifiers - MO TU WE. */ + recur.freq = ICAL_WEEKLY_RECURRENCE; + p = rrule_parse_interval (s + 1, &recur, &error_message); + p = rrule_parse_weekly_days (p, &recur, &error_message); + p = rrule_parse_duration (p, &recur, &error_message); + } else if (*s == 'M' && *(s + 1) == 'D') { + /* The MONTHLY By Day RRULE has day number modifiers - 1 1- LD. */ + recur.freq = ICAL_MONTHLY_RECURRENCE; + p = rrule_parse_interval (s + 2, &recur, &error_message); + p = rrule_parse_monthly_days (p, &recur, &error_message); + p = rrule_parse_duration (p, &recur, &error_message); + } else if (*s == 'M' && *(s + 1) == 'P') { + /* The MONTHLY By Position RRULE has position modifiers - 1 2- and + weekday modifiers - MO TU. */ + recur.freq = ICAL_MONTHLY_RECURRENCE; + p = rrule_parse_interval (s + 2, &recur, &error_message); + p = rrule_parse_monthly_positions (p, &recur, &error_message); + p = rrule_parse_duration (p, &recur, &error_message); + } else if (*s == 'Y' && *(s + 1) == 'M') { + /* The YEARLY By Month RRULE has month modifiers - 1 3 12. */ + recur.freq = ICAL_YEARLY_RECURRENCE; + p = rrule_parse_interval (s + 2, &recur, &error_message); + p = rrule_parse_yearly_months (p, &recur, &error_message); + p = rrule_parse_duration (p, &recur, &error_message); + } else if (*s == 'Y' && *(s + 1) == 'D') { + /* The YEARLY By Day RRULE has day number modifiers - 100 200. */ + recur.freq = ICAL_YEARLY_RECURRENCE; + p = rrule_parse_interval (s + 2, &recur, &error_message); + p = rrule_parse_yearly_days (p, &recur, &error_message); + p = rrule_parse_duration (p, &recur, &error_message); + } else { + error_message = "Invalid RRULE Frequency"; } + if (error_message) { + prop = create_parse_error_property (error_message, property_name, s); + } else { + if (!strcmp (property_name, "RRULE")) + prop = icalproperty_new_rrule (recur); + else + prop = icalproperty_new_exrule (recur); + } + + if (free_string) + deleteStr (s); + + return (void*)prop; +} + + + +/* directly convertable property. The string representation of vcal is + the same as ical */ + +void* dc_prop(int icaltype, VObject *object, icalcomponent *comp, + icalvcal_defaults *defaults) +{ + icalproperty_kind kind = (icalproperty_kind)icaltype; + icalproperty *prop; + icalvalue *value; + icalvalue_kind value_kind; + char *s; + int free_string; + + prop = icalproperty_new(kind); + + value_kind = icalenum_property_kind_to_value_kind (icalproperty_isa(prop)); + + s = get_string_value (object, &free_string); + + value = icalvalue_new_from_string(value_kind,s); + + if (free_string) + deleteStr (s); + icalproperty_set_value(prop,value); return (void*)prop; @@ -320,35 +1415,72 @@ struct conversion_table_struct conversion_table[] = {VCCalProp, COMPONENT, comp, ICAL_VCALENDAR_COMPONENT}, {VCTodoProp, COMPONENT, comp, ICAL_VTODO_COMPONENT}, {VCEventProp, COMPONENT, comp, ICAL_VEVENT_COMPONENT}, + {VCAAlarmProp, COMPONENT, alarm_comp, ICAL_XAUDIOALARM_COMPONENT}, {VCDAlarmProp, COMPONENT, alarm_comp, ICAL_XDISPLAYALARM_COMPONENT}, {VCMAlarmProp, COMPONENT, alarm_comp, ICAL_XEMAILALARM_COMPONENT}, {VCPAlarmProp, COMPONENT, alarm_comp, ICAL_XPROCEDUREALARM_COMPONENT}, -{VCDayLightProp, COMPONENT, daylight_comp,0}, -{VCTimeZoneProp, COMPONENT, timezone_comp, ICAL_VTIMEZONE_COMPONENT}, -{VCProdIdProp, PROPERTY, dc_prop, ICAL_PRODID_PROPERTY}, + +/* These can all be converted directly by parsing the string into a libical + value. */ {VCClassProp, PROPERTY, dc_prop, ICAL_CLASS_PROPERTY}, -{VCDCreatedProp, PROPERTY, dc_prop, ICAL_CREATED_PROPERTY}, {VCDescriptionProp, PROPERTY, dc_prop, ICAL_DESCRIPTION_PROPERTY}, {VCAttendeeProp, PROPERTY, dc_prop, ICAL_ATTENDEE_PROPERTY}, -{VCCategoriesProp, PROPERTY, categories_prop,ICAL_CATEGORIES_PROPERTY}, {VCDTendProp, PROPERTY, dc_prop, ICAL_DTEND_PROPERTY}, {VCDTstartProp, PROPERTY, dc_prop, ICAL_DTSTART_PROPERTY}, {VCDueProp, PROPERTY, dc_prop, ICAL_DUE_PROPERTY}, {VCLocationProp, PROPERTY, dc_prop, ICAL_LOCATION_PROPERTY}, {VCSummaryProp, PROPERTY, dc_prop, ICAL_SUMMARY_PROPERTY}, -{VCVersionProp, PROPERTY, dc_prop, ICAL_VERSION_PROPERTY}, -{VCTranspProp, PROPERTY, transp_prop, ICAL_TRANSP_PROPERTY}, {VCUniqueStringProp, PROPERTY, dc_prop, ICAL_UID_PROPERTY}, {VCURLProp, PROPERTY, dc_prop, ICAL_URL_PROPERTY}, -{VCLastModifiedProp, PROPERTY, dc_prop, ICAL_LASTMODIFIED_PROPERTY}, -{VCSequenceProp, PROPERTY, dc_prop, ICAL_SEQUENCE_PROPERTY}, {VCPriorityProp, PROPERTY, dc_prop, ICAL_PRIORITY_PROPERTY}, + +/* These can contain multiple values, which are separated in ';' in vCalendar + but ',' in iCalendar. */ +{VCCategoriesProp, PROPERTY, multivalued_prop,ICAL_CATEGORIES_PROPERTY}, +{VCRDateProp, PROPERTY, multivalued_prop,ICAL_RDATE_PROPERTY}, +{VCExpDateProp, PROPERTY, multivalued_prop,ICAL_EXDATE_PROPERTY}, + +/* These can be in floating time in vCalendar, but must be in UTC in iCalendar. + */ +{VCDCreatedProp, PROPERTY, utc_datetime_prop,ICAL_CREATED_PROPERTY}, +{VCLastModifiedProp, PROPERTY, utc_datetime_prop,ICAL_LASTMODIFIED_PROPERTY}, +{VCCompletedProp, PROPERTY, utc_datetime_prop,ICAL_COMPLETED_PROPERTY}, + +{VCTranspProp, PROPERTY, transp_prop, ICAL_TRANSP_PROPERTY}, +{VCSequenceProp, PROPERTY, sequence_prop, ICAL_SEQUENCE_PROPERTY}, {VCStatusProp, PROPERTY, status_prop, ICAL_STATUS_PROPERTY}, +{VCRRuleProp, PROPERTY, rule_prop, ICAL_RRULE_PROPERTY}, +{VCXRuleProp, PROPERTY, rule_prop, ICAL_EXRULE_PROPERTY}, + {VCRSVPProp, UNSUPPORTED, rsvp_parameter,ICAL_RSVP_PARAMETER }, {VCEncodingProp, UNSUPPORTED, parameter, ICAL_ENCODING_PARAMETER}, {VCRoleProp, UNSUPPORTED, parameter, ICAL_ROLE_PARAMETER}, -{VCStatusProp, UNSUPPORTED, parameter, ICAL_STATUS_PROPERTY}, + +/* We don't want the old VERSION or PRODID properties copied across as they + are now incorrect. New VERSION & PRODID properties are added instead. */ +{VCVersionProp, IGNORE, 0, 0}, +{VCProdIdProp, IGNORE, 0, 0}, + +/* We ignore DAYLIGHT and TZ properties of the toplevel object, since we can't + really do much with them. */ +{VCDayLightProp, IGNORE, 0, 0}, +{VCTimeZoneProp, IGNORE, 0, 0}, + +/* These are all alarm properties. We handle these when the alarm component + is created, so we ignore them when doing the automatic conversions. + "TYPE" is used in AALARM, but doesn't seem to have a name in vobject.h. */ +{"TYPE", IGNORE,0, 0}, +{VCRunTimeProp, IGNORE,0, 0}, +{VCSnoozeTimeProp, IGNORE,0, 0}, +{VCRepeatCountProp, IGNORE,0, 0}, +{VCValueProp, IGNORE,0, 0}, +{VCAudioContentProp, IGNORE,0, 0}, +{VCProcedureNameProp, IGNORE,0, 0}, +{VCDisplayStringProp, IGNORE,0, 0}, +{VCEmailAddressProp, IGNORE,0, 0}, +{VCNoteProp, IGNORE,0, 0}, + {VCQuotedPrintableProp,UNSUPPORTED,0, 0}, {VC7bitProp, UNSUPPORTED,0, 0}, {VC8bitProp, UNSUPPORTED,0, 0}, @@ -360,7 +1492,6 @@ struct conversion_table_struct conversion_table[] = {VCAppleLinkProp, UNSUPPORTED,0, 0}, {VCAttachProp, UNSUPPORTED,0, 0}, {VCATTMailProp, UNSUPPORTED,0, 0}, -{VCAudioContentProp, UNSUPPORTED,0, 0}, {VCAVIProp, UNSUPPORTED,0, 0}, {VCBase64Prop, UNSUPPORTED,0, 0}, {VCBBSProp, UNSUPPORTED,0, 0}, @@ -376,18 +1507,14 @@ struct conversion_table_struct conversion_table[] = {VCCISProp, UNSUPPORTED,0, 0}, {VCCityProp, UNSUPPORTED,0, 0}, {VCCommentProp, UNSUPPORTED,0, 0}, -{VCCompletedProp, UNSUPPORTED,0, 0}, {VCCountryNameProp, UNSUPPORTED,0, 0}, {VCDataSizeProp, UNSUPPORTED,0, 0}, {VCDeliveryLabelProp, UNSUPPORTED,0, 0}, {VCDIBProp, UNSUPPORTED,0, 0}, -{VCDisplayStringProp, UNSUPPORTED,0, 0}, {VCDomesticProp, UNSUPPORTED,0, 0}, -{VCEmailAddressProp, UNSUPPORTED,0, 0}, {VCEndProp, UNSUPPORTED,0, 0}, {VCEWorldProp, UNSUPPORTED,0, 0}, {VCExNumProp, UNSUPPORTED,0, 0}, -{VCExpDateProp, UNSUPPORTED,0, 0}, {VCExpectProp, UNSUPPORTED,0, 0}, {VCFamilyNameProp, UNSUPPORTED,0, 0}, {VCFaxProp, UNSUPPORTED,0, 0}, @@ -418,7 +1545,6 @@ struct conversion_table_struct conversion_table[] = {VCNamePrefixesProp, UNSUPPORTED,0, 0}, {VCNameProp, UNSUPPORTED,0, 0}, {VCNameSuffixesProp, UNSUPPORTED,0, 0}, -{VCNoteProp, UNSUPPORTED,0, 0}, {VCOrgNameProp, UNSUPPORTED,0, 0}, {VCOrgProp, UNSUPPORTED,0, 0}, {VCOrgUnit2Prop, UNSUPPORTED,0, 0}, @@ -439,21 +1565,15 @@ struct conversion_table_struct conversion_table[] = {VCPostalProp, UNSUPPORTED,0, 0}, {VCPowerShareProp, UNSUPPORTED,0, 0}, {VCPreferredProp, UNSUPPORTED,0, 0}, -{VCProcedureNameProp, UNSUPPORTED,0, 0}, {VCProdigyProp, UNSUPPORTED,0, 0}, {VCPronunciationProp, UNSUPPORTED,0, 0}, {VCPSProp, UNSUPPORTED,0, 0}, {VCPublicKeyProp, UNSUPPORTED,0, 0}, {VCQPProp, UNSUPPORTED,0, 0}, {VCQuickTimeProp, UNSUPPORTED,0, 0}, -{VCRDateProp, UNSUPPORTED,0, 0}, {VCRegionProp, UNSUPPORTED,0, 0}, -{VCRepeatCountProp, UNSUPPORTED,0, 0}, {VCResourcesProp, UNSUPPORTED,0, 0}, {VCRNumProp, UNSUPPORTED,0, 0}, -{VCRRuleProp, UNSUPPORTED,0, 0}, -{VCRunTimeProp, UNSUPPORTED,0, 0}, -{VCSnoozeTimeProp, UNSUPPORTED,0, 0}, {VCStartProp, UNSUPPORTED,0, 0}, {VCStreetAddressProp, UNSUPPORTED,0, 0}, {VCSubTypeProp, UNSUPPORTED,0, 0}, @@ -462,7 +1582,6 @@ struct conversion_table_struct conversion_table[] = {VCTitleProp, UNSUPPORTED,0, 0}, {VCTLXProp, UNSUPPORTED,0, 0}, {VCURLValueProp, UNSUPPORTED,0, 0}, -{VCValueProp, UNSUPPORTED,0, 0}, {VCVideoProp, UNSUPPORTED,0, 0}, {VCVoiceProp, UNSUPPORTED,0, 0}, {VCWAVEProp, UNSUPPORTED,0, 0}, @@ -470,7 +1589,7 @@ struct conversion_table_struct conversion_table[] = {VCWorkProp, UNSUPPORTED,0, 0}, {VCX400Prop, UNSUPPORTED,0, 0}, {VCX509Prop, UNSUPPORTED,0, 0}, -{VCXRuleProp, UNSUPPORTED,0, 0}, + {0,0,0,0} }; diff --git a/libical/src/libicalvcal/icalvcal.h b/libical/src/libicalvcal/icalvcal.h index f2316c2d0f..96e94b9f2e 100644 --- a/libical/src/libicalvcal/icalvcal.h +++ b/libical/src/libicalvcal/icalvcal.h @@ -29,10 +29,25 @@ #include "ical.h" #include "vcc.h" +/* These are used as default values if the values are missing in the vCalendar + file. Gnome Calendar, for example, does not save the URL of the audio alarm, + so we have to add a value here to make a valid iCalendar object. */ +typedef struct _icalvcal_defaults icalvcal_defaults; +struct _icalvcal_defaults { + char *alarm_audio_url; + char *alarm_audio_fmttype; + char *alarm_description; +}; + + /* Convert a vObject into an icalcomponent */ icalcomponent* icalvcal_convert(VObject *object); +icalcomponent* icalvcal_convert_with_defaults (VObject *object, + icalvcal_defaults *defaults); + + #endif /* !ICALVCAL_H */ diff --git a/libical/src/libicalvcal/vcc.y b/libical/src/libicalvcal/vcc.y index 70feefab57..4397198b1b 100644 --- a/libical/src/libicalvcal/vcc.y +++ b/libical/src/libicalvcal/vcc.y @@ -414,9 +414,31 @@ static void enterValues(const char *value) } else { if (value) { - setVObjectUStringZValue_(curProp,fakeUnicode(value,0)); + char *p1, *p2; + wchar_t *p3; + int i; + + /* If the property already has a string value, we append this one, + using ';' to separate the values. */ + if (vObjectUStringZValue(curProp)) { + p1 = fakeCString(vObjectUStringZValue(curProp)); + p2 = malloc((strlen(p1)+strlen(value)+1)); + strcpy(p2, p1); + deleteStr(p1); + + i = strlen(p2); + p2[i] = ';'; + p2[i+1] = '\0'; + p2 = strcat(p2, value); + p3 = (wchar_t *) vObjectUStringZValue(curProp); + free(p3); + setVObjectUStringZValue_(curProp,fakeUnicode(p2,0)); + deleteStr(p2); + } else { + setVObjectUStringZValue_(curProp,fakeUnicode(value,0)); } } + } deleteStr(value); } -- cgit