/* * xmlformat-recurrence - common code for recurrence implementation * Copyright (C) 2004-2005 Armin Bauer * Copyright (C) 2007 Daniel Gollub * Copyright (C) 2007 Christopher Stender * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ #include "xmlformat-common.h" /* * Basic Recurrence Rules (vCalendar) * * The functions below are necessary for converting a vCalendar Recurrence Rule * to a xmlformat-event. * * Description: * convert_vcal_rrule_frequency get frequency value * convert_vcal_rrule_freqmod get frequency modifier * convert_vcal_rrule_countuntil get count or until value * convert_vcal_rrule_to_xml get interval and call functions above */ static int convert_vcal_rrule_frequency(OSyncXMLField *xmlfield, const char *rule) { int frequency_state = 0; char next = *(rule + 1); char *frequency = NULL; /* get frequency: only D(1), W(2), MP(3), MD(4), YD(5) and YM(6) are allowed */ if (*rule == 'D') { frequency_state = 1; frequency = "DAILY"; } else if (*rule == 'W') { frequency_state = 2; frequency = "WEEKLY"; } else if (*rule == 'M' && next == 'P') { frequency_state = 3; frequency = "MONTHLY"; } else if (*rule == 'M' && next == 'D') { frequency_state = 4; frequency = "MONTHLY"; } else if (*rule == 'Y' && next == 'D') { frequency_state = 5; frequency = "YEARLY"; } else if (*rule == 'Y' && next == 'M') { frequency_state = 6; frequency = "YEARLY"; } else { osync_trace(TRACE_INTERNAL, "invalid or missing frequency"); return -1; } /* set Frequency */ FIXME_xmlfield_set_key_value(xmlfield, "Frequency", frequency); return frequency_state; } static char *convert_vcal_rrule_freqmod(OSyncXMLField *xmlfield, gchar **rule, int size, int freqstate) { int i; GString *fm_buffer = g_string_new(""); /* for each modifier do... */ for(i=1; i < size-1; i++) { int count; char sign; if(fm_buffer->len > 0) g_string_append(fm_buffer, ","); /* check frequency modifier */ if (sscanf(rule[i], "%d%c" , &count, &sign) == 2) { /* we need to convert $COUNT- to -$COUNT -> RFC2445 */ if (sign == '-') count = -count; g_string_append_printf(fm_buffer, "%d", count); /* if first freqmod is "(-)2" and second one is "TU" we * have to convert it to 2TU */ if (i < size-2 && !sscanf(rule[i+1], "%d", &count)) { g_string_append_printf(fm_buffer, "%s", rule[i+1]); i++; } } else { /* e.g. Day or 'LD' (Last day) */ g_string_append(fm_buffer, rule[i]); } } return g_string_free(fm_buffer, FALSE); } static void convert_vcal_rrule_countuntil(OSyncXMLField *xmlfield, const char *duration_block) { int count; int offset = 0; char *until = NULL; /* COUNT: #20 */ if (sscanf(duration_block, "#%d", &count) == 1) { FIXME_xmlfield_set_key_value(xmlfield, "Count", duration_block+1); return; } /* UNTIL: 20070515T120000Z */ if (!osync_time_isdate(duration_block)) { /* Check if this duration_block is a localtime timestamp. * If it is not UTC change the offset from 0 to the system UTC offset.· * vcal doesn't store any TZ information. This means the device have to be * in the same Timezone as the host. */ /* FIXME: For now we ignore all osync_time_* OSyncErrors */ OSyncError *error = NULL; if (!osync_time_isutc(duration_block)) { struct tm *ttm = osync_time_vtime2tm(duration_block, &error); offset = osync_time_timezone_diff(ttm, &error); g_free(ttm); } until = osync_time_vtime2utc(duration_block, offset, &error); } else { until = g_strdup(duration_block); } FIXME_xmlfield_set_key_value(xmlfield, "Until", until); g_free(until); } OSyncXMLField *convert_vcal_rrule_to_xml(OSyncXMLFormat *xmlformat, VFormatAttribute *attr, const char *rulename, OSyncError **error) { OSyncXMLField *xmlfield = osync_xmlfield_new(xmlformat, rulename, error); if(!xmlfield) { osync_trace(TRACE_ERROR, "%s: %s" , __func__, osync_error_print(error)); return NULL; } const char *rule = vformat_attribute_get_nth_value(attr, 0); osync_trace(TRACE_INTERNAL, "converting vcal rrule '%s' to xml", rule); int frequency_state = 0, counter = 0; char *frequency_block = NULL, *freq_mod = NULL, *duration_block = NULL; gchar** blocks = g_strsplit(rule, " ", 256); /* count blocks, e.g. (W2 TU TH #5) -> 4 blocks */ for(; blocks[counter]; counter++); frequency_block = blocks[0]; duration_block = blocks[counter-1]; /* get frequency */ frequency_state = convert_vcal_rrule_frequency(xmlfield, frequency_block); /* get count or until value */ convert_vcal_rrule_countuntil(xmlfield, duration_block); /* the interval value is at the end of the frequency_block */ frequency_block++; if (frequency_state > 2) frequency_block++; /* set Interval */ FIXME_xmlfield_set_key_value(xmlfield, "Interval", frequency_block); /* get Frequency modifier (ByDay, ByMonthDay, etc. */ if (counter > 2) freq_mod = convert_vcal_rrule_freqmod(xmlfield, blocks, counter, frequency_state); // TODO enum /* W(2), MP(3), MD(4), YD(5) and YM(6) */ switch(frequency_state) { case 2: case 3: FIXME_xmlfield_set_key_value(xmlfield, "ByDay", freq_mod); break; case 4: FIXME_xmlfield_set_key_value(xmlfield, "ByMonthDay", freq_mod); break; case 5: FIXME_xmlfield_set_key_value(xmlfield, "ByYearDay", freq_mod); break; case 6: FIXME_xmlfield_set_key_value(xmlfield, "ByMonth", freq_mod); break; default: break; } g_strfreev(blocks); return xmlfield; } /* * Basic Recurrence Rules * * The functions below are necessary for converting a xmlformat-event * to a vCalendar Recurrence Rule. * * Description: * convert_rrule_vcal_frequency get frequency value * convert_rrule_vcal_freqmod get frequency modifier * convert_rrule_vcal_countuntil get count or until value * convert_xml_rrule_to_vcal get interval and call functions above */ static const char *convert_rrule_vcal_frequency(const char *frequency, int freq_mod_id) { const char *frequency_id = NULL; /* vcal frequency: only D(1), W(2), MP(3), MD(4), YD(5) and YM(6) are allowed */ if (frequency) { if (!strcmp(frequency, "DAILY")) { frequency_id = "D"; } else if (!strcmp(frequency, "WEEKLY")) { frequency_id = "W"; } else if (!strcmp(frequency, "MONTHLY") && freq_mod_id == 0) { frequency_id = "MD"; } else if (!strcmp(frequency, "MONTHLY") && freq_mod_id == 3) { frequency_id = "MP"; } else if (!strcmp(frequency, "MONTHLY") && freq_mod_id == 4) { frequency_id = "MD"; } else if (!strcmp(frequency, "YEARLY") && freq_mod_id == 0) { frequency_id = "YD"; } else if (!strcmp(frequency, "YEARLY") && freq_mod_id == 5) { frequency_id = "YD"; } else if (!strcmp(frequency, "YEARLY") && freq_mod_id == 6) { frequency_id = "YM"; } else { osync_trace(TRACE_ERROR, "invalid frequency"); } } else { osync_trace(TRACE_ERROR, "missing frequency"); } return frequency_id; } #if 0 static char *convert_rrule_vcal_freqmod(OSyncXMLField *xmlfield, gchar **rule, int size, int freqstate) { int i; GString *fm_buffer = g_string_new(""); /* for each modifier do... */ for(i=1; i < size-1; i++) { int count; char sign; if(fm_buffer->len > 0) g_string_append(fm_buffer, ","); /* check frequency modifier */ if (sscanf(rule[i], "%d%c" , &count, &sign) == 2) { /* we need to convert $COUNT- to -$COUNT -> RFC2445 */ if (sign == '-') count = -count; g_string_append_printf(fm_buffer, "%d", count); /* if first freqmod is "(-)2" and second one is "TU" we * have to convert it to 2TU */ if (i < size-2 && !sscanf(rule[i+1], "%d", &count)) { g_string_append_printf(fm_buffer, "%s", rule[i+1]); i++; } } else { /* e.g. Day or 'LD' (Last day) */ g_string_append(fm_buffer, rule[i]); } } return g_string_free(fm_buffer, FALSE); } #endif static char *convert_rrule_vcal_until(const char *until_utc) { int offset = 0; char *until = NULL; /* UNTIL: 20070515T120000 */ /* It is UTC : change the offset from 0 to the system UTC offset.· * vcal doesn't store any TZ information. This means the device have to be * in the same Timezone as the host. */ /* FIXME: For now we ignore all osync_time_* OSyncErrors */ OSyncError *error = NULL; struct tm *ttm = osync_time_vtime2tm(until_utc, &error); offset = osync_time_timezone_diff(ttm, &error); g_free(ttm); until = osync_time_vtime2localtime(until_utc, offset, &error); return until; } VFormatAttribute *convert_xml_rrule_to_vcal(VFormat *vformat, OSyncXMLField *xmlfield, const char *rulename, const char *encoding) { VFormatAttribute *attr = vformat_attribute_new(NULL, rulename); const char *interval = NULL; const char *frequency = NULL; const char *until_utc = NULL; const char *count = NULL; const char *freq_mod = NULL; int freq_mod_id = 0; char *rule = NULL; int numberofkeys, i; /* parse xmlfield */ numberofkeys = osync_xmlfield_get_key_count(xmlfield); for(i=0; i < numberofkeys; i++) { const char *name = osync_xmlfield_get_nth_key_name(xmlfield, i); const char *value = osync_xmlfield_get_nth_key_value(xmlfield, i); if(!strcmp(name, "Interval")) { interval = value; } else if(!strcmp(name, "Frequency")) { frequency = value; } else if(!strcmp(name, "Until")) { until_utc = value; } else if(!strcmp(name, "Count")) { count = value; } else if(!strcmp(name, "ByDay")) { freq_mod_id = 3; freq_mod = value; } else if(!strcmp(name, "ByMonthDay")) { freq_mod_id = 4; freq_mod = value; } else if(!strcmp(name, "ByYearDay")) { freq_mod_id = 5; freq_mod = value; } else if(!strcmp(name, "ByMonth")) { freq_mod_id = 6; freq_mod = value; } /* TODO: handle advanced frequency modifiers */ } /* get vcal frequency */ frequency = convert_rrule_vcal_frequency(frequency, freq_mod_id); if (frequency && interval) rule = g_strdup_printf("%s%s", frequency, interval); if (freq_mod) { /* TODO: handle advanced frequency modifiers with * a new function convert_rrule_vcal_freqmod */ rule = g_strdup_printf("%s %s", rule, freq_mod); } if (until_utc) { char *until = convert_rrule_vcal_until(until_utc); rule = g_strdup_printf("%s %s", rule, until); } if (count) { rule = g_strdup_printf("%s #%s", rule, count); } vformat_attribute_add_value(attr, rule); vformat_add_attribute(vformat, attr); return attr; } // End of Basic Recurrence Rule /* * Basic & Extended Recurrence Rules (iCalendar) * * The functions below are necessary for converting an iCalendar Recurrence Rule * to a xmlformat-event. * * Description: * convert_ical_rrule_to_xml converts an ical rrule to xmlformat */ OSyncXMLField *convert_ical_rrule_to_xml(OSyncXMLFormat *xmlformat, VFormatAttribute *attr, const char *rulename, OSyncError **error) { OSyncXMLField *xmlfield = osync_xmlfield_new(xmlformat, rulename, error); if (!xmlfield) goto error; osync_bool extended = FALSE; typedef struct { char *name; char *value; } rrule_t; rrule_t rrules[14]; memset(rrules, 0, sizeof(rrules)); rrules[0].name = "Frequency"; rrules[1].name = "Until"; rrules[2].name = "Count"; rrules[3].name = "Interval"; rrules[4].name = "BySecond"; rrules[5].name = "ByMinute"; rrules[6].name = "ByHour"; rrules[7].name = "ByDay"; rrules[8].name = "ByMonthDay"; rrules[9].name = "ByYearDay"; rrules[10].name = "ByWeekNo"; rrules[11].name = "ByMonth"; rrules[12].name = "BySetPos"; rrules[13].name = "WKST"; // parse values GList *values = vformat_attribute_get_values_decoded(attr); for (; values; values = values->next) { GString *retstr = values->data; g_assert(retstr); if (strstr(retstr->str, "FREQ=")) { rrules[0].value = retstr->str + strlen("FREQ="); } else if (strstr(retstr->str, "UNTIL=")) { rrules[1].value = retstr->str + strlen("UNTIL="); } else if (strstr(retstr->str, "COUNT=")) { rrules[2].value = retstr->str + strlen("COUNT="); } else if (strstr(retstr->str, "INTERVAL=")) { rrules[3].value = retstr->str + strlen("INTERVAL="); } else if (strstr(retstr->str, "BYSECOND=")) { rrules[4].value = retstr->str + strlen("BYSECOND="); extended = TRUE; } else if (strstr(retstr->str, "BYMINUTE=")) { rrules[5].value = retstr->str + strlen("BYMINUTE="); extended = TRUE; } else if (strstr(retstr->str, "BYHOUR=")) { rrules[6].value = retstr->str + strlen("BYHOUR="); extended = TRUE; } else if (strstr(retstr->str, "BYDAY=")) { rrules[7].value = retstr->str + strlen("BYDAY="); } else if (strstr(retstr->str, "BYMONTHDAY=")) { rrules[8].value = retstr->str + strlen("BYMONTHDAY="); } else if (strstr(retstr->str, "BYYEARDAY=")) { rrules[9].value = retstr->str + strlen("BYYEARDAY="); } else if (strstr(retstr->str, "BYWEEKNO=")) { rrules[10].value = retstr->str + strlen("BYWEEKNO="); extended = TRUE; } else if (strstr(retstr->str, "BYMONTH=")) { rrules[11].value = retstr->str + strlen("BYMONTH="); } else if (strstr(retstr->str, "BYSETPOS=")) { rrules[12].value = retstr->str + strlen("BYSETPOS="); extended = TRUE; } else if (strstr(retstr->str, "WKST=")) { rrules[13].value = retstr->str + strlen("WKST="); extended = TRUE; } } // rename xmlfield if extended is true if (extended) { if (!strcmp(rulename, "ExceptionRule")) osync_xmlfield_set_name(xmlfield, "ExceptionRuleExtended"); else if (!strcmp(rulename, "RecurrenceRule")) osync_xmlfield_set_name(xmlfield, "RecurrenceRuleExtended"); } // set interval to 1, if it wasn't set before if (rrules[3].value == NULL) rrules[3].value = "1"; int i; for (i = 0; i <= 13; i++) { if (rrules[i].value != NULL) if (!osync_xmlfield_add_key_value(xmlfield, rrules[i].name, rrules[i].value, error)) goto error; } return xmlfield; error: osync_trace(TRACE_ERROR, "%s: %s" , __func__, osync_error_print(error)); return NULL; } /* * Basic & Extended Recurrence Rules (iCalendar) * * The functions below are necessary for converting a xmlformat-event * to a iCalendar Recurrence Rule. * * Description: * convert_xml_rrule_to_ical converts a rrule in xmlformat to ical */ VFormatAttribute *convert_xml_rrule_to_ical(VFormat *vformat, OSyncXMLField *xmlfield, const char *rulename, const char *encoding) { VFormatAttribute *attr = vformat_attribute_new(NULL, rulename); int i, attributes = osync_xmlfield_get_key_count(xmlfield); for (i = 0; i < attributes; i++) { const char *attrname = osync_xmlfield_get_nth_key_name(xmlfield, i); const char *attrvalue = osync_xmlfield_get_nth_key_value(xmlfield, i); if (attrname != NULL && attrvalue != NULL) { GString *value = g_string_new(""); if (!strcmp(attrname, "Frequency")) { value = g_string_append(value, "FREQ="); } else if (!strcmp(attrname, "Until")) { value = g_string_append(value, "UNTIL="); } else if (!strcmp(attrname, "Count")) { value = g_string_append(value, "COUNT="); } else if (!strcmp(attrname, "Interval")) { value = g_string_append(value, "INTERVAL="); } else if (!strcmp(attrname, "BySecond")) { value = g_string_append(value, "BYSECOND="); } else if (!strcmp(attrname, "ByMinute")) { value = g_string_append(value, "BYMINUTE="); } else if (!strcmp(attrname, "ByHour")) { value = g_string_append(value, "BYHOUR="); } else if (!strcmp(attrname, "ByDay")) { value = g_string_append(value, "BYDAY="); } else if (!strcmp(attrname, "ByMonthDay")) { value = g_string_append(value, "BYMONTHDAY="); } else if (!strcmp(attrname, "ByYearDay")) { value = g_string_append(value, "BYYEARDAY="); } else if (!strcmp(attrname, "ByWeekNo")) { value = g_string_append(value, "BYWEEKNO="); } else if (!strcmp(attrname, "ByMonth")) { value = g_string_append(value, "BYMONTH="); } else if (!strcmp(attrname, "BySetPos")) { value = g_string_append(value, "BYSETPOS="); } else if (!strcmp(attrname, "WKST")) { value = g_string_append(value, "WKST="); } else { osync_trace(TRACE_INTERNAL, "WARNING: found unknown value: %s", attrname); g_string_free(value, TRUE); continue; } value = g_string_append(value, attrvalue); vformat_attribute_add_value(attr, value->str); g_string_free(value, TRUE); } } vformat_add_attribute(vformat, attr); return attr; }