aboutsummaryrefslogtreecommitdiffstats
path: root/plugins/save-calendar/csv-format.c
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/save-calendar/csv-format.c')
-rw-r--r--plugins/save-calendar/csv-format.c638
1 files changed, 638 insertions, 0 deletions
diff --git a/plugins/save-calendar/csv-format.c b/plugins/save-calendar/csv-format.c
new file mode 100644
index 0000000000..1e1bf4a7e1
--- /dev/null
+++ b/plugins/save-calendar/csv-format.c
@@ -0,0 +1,638 @@
+/*
+ * This program 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 of the License, or (at your option) version 3.
+ *
+ * This program 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ * Philip Van Hoof <pvanhoof@gnome.org>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <glib/gi18n.h>
+
+#include "format-handler.h"
+
+typedef struct _CsvConfig CsvConfig;
+struct _CsvConfig {
+ gchar *newline;
+ gchar *quote;
+ gchar *delimiter;
+ gboolean header;
+};
+
+static gboolean string_needsquotes (const gchar *value, CsvConfig *config);
+
+typedef struct _CsvPluginData CsvPluginData;
+struct _CsvPluginData
+{
+ GtkWidget *delimiter_entry, *newline_entry, *quote_entry, *header_check;
+};
+
+static void
+display_error_message (GtkWidget *parent,
+ GError *error)
+{
+ GtkWidget *dialog;
+
+ dialog = gtk_message_dialog_new (
+ GTK_WINDOW (parent), 0,
+ GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
+ "%s", error->message);
+ gtk_dialog_run (GTK_DIALOG (dialog));
+ gtk_widget_destroy (dialog);
+}
+
+enum { /* CSV helper enum */
+ ECALCOMPONENTTEXT,
+ ECALCOMPONENTATTENDEE,
+ CONSTCHAR
+};
+
+/* Some helpers for the csv stuff */
+static GString *
+add_list_to_csv (GString *line,
+ GSList *list_in,
+ CsvConfig *config,
+ gint type)
+{
+
+ /*
+ * This one will write 'ECalComponentText' and 'const char' GSLists. It will
+ * put quotes around the complete written value if there's was only one value
+ * but it required having quotes and if there was more than one value (in which
+ * case delimiters are used to separate them, hence the need for the quotes).
+ */
+
+ if (list_in) {
+ gboolean needquotes = FALSE;
+ GSList *list = list_in;
+ GString *tmp = NULL;
+ gint cnt = 0;
+ while (list) {
+ const gchar *str = NULL;
+ if (cnt == 0)
+ tmp = g_string_new ("");
+ if (cnt > 0)
+ needquotes = TRUE;
+ switch (type) {
+ case ECALCOMPONENTATTENDEE:
+ str = ((ECalComponentAttendee *) list->data)->value;
+ break;
+ case ECALCOMPONENTTEXT:
+ str = ((ECalComponentText *) list->data)->value;
+ break;
+ case CONSTCHAR:
+ default:
+ str = list->data;
+ break;
+ }
+ if (!needquotes)
+ needquotes = string_needsquotes (str, config);
+ if (str)
+ tmp = g_string_append (tmp, (const gchar *) str);
+ list = g_slist_next (list); cnt++;
+ if (list)
+ tmp = g_string_append (tmp, config->delimiter);
+ }
+
+ if (needquotes)
+ line = g_string_append (line, config->quote);
+ line = g_string_append_len (line, tmp->str, tmp->len);
+ g_string_free (tmp, TRUE);
+ if (needquotes)
+ line = g_string_append (line, config->quote);
+ }
+
+ line = g_string_append (line, config->delimiter);
+ return line;
+}
+
+static GString *
+add_nummeric_to_csv (GString *line,
+ gint *nummeric,
+ CsvConfig *config)
+{
+
+ /*
+ * This one will write {-1}..{00}..{01}..{99}
+ * it prepends a 0 if it's < 10 and > -1
+ */
+
+ if (nummeric)
+ g_string_append_printf (
+ line, "%s%d",
+ (*nummeric < 10 && *nummeric > -1) ? "0" : "",
+ *nummeric);
+
+ return g_string_append (line, config->delimiter);
+}
+
+static GString *
+add_time_to_csv (GString *line,
+ icaltimetype *time,
+ CsvConfig *config)
+{
+
+ if (time) {
+ gboolean needquotes = FALSE;
+ struct tm mytm = icaltimetype_to_tm (time);
+ gchar *str = (gchar *) g_malloc (sizeof (gchar) * 200);
+
+ /* Translators: the %F %T is the third argument for a
+ * strftime function. It lets you define the formatting
+ * of the date in the csv-file. */
+ e_utf8_strftime (str, 200, _("%F %T"), &mytm);
+
+ needquotes = string_needsquotes (str, config);
+
+ if (needquotes)
+ line = g_string_append (line, config->quote);
+
+ line = g_string_append (line, str);
+
+ if (needquotes)
+ line = g_string_append (line, config->quote);
+
+ g_free (str);
+
+ }
+
+ line = g_string_append (line, config->delimiter);
+
+ return line;
+}
+
+static gboolean
+string_needsquotes (const gchar *value,
+ CsvConfig *config)
+{
+
+ /* This is the actual need for quotes-checker */
+
+ /*
+ * These are the simple substring-checks
+ *
+ * Example: {Mom, can you please do that for me?}
+ * Will be written as {"Mom, can you please do that for me?"}
+ */
+
+ gboolean needquotes = strstr (value, config->delimiter) ? TRUE : FALSE;
+
+ if (!needquotes) {
+ needquotes = strstr (value, config->newline) ? TRUE : FALSE;
+ if (!needquotes)
+ needquotes = strstr (value, config->quote) ? TRUE : FALSE;
+ }
+
+ /*
+ * If the special-char is char+onespace (so like {, } {" }, {\n }) and it occurs
+ * the value that is going to be written
+ *
+ * In this case we don't trust the user . . . and are going to quote the string
+ * just to play save -- Quoting is always allowed in the CSV format. If you can
+ * avoid it, it's better to do so since a lot applications don't support CSV
+ * correctly! --.
+ *
+ * Example: {Mom,can you please do that for me?}
+ * This example will be written as {"Mom,can you please do that for me?"} because
+ * there's a {,} behind {Mom} and the delimiter is {, } (so we searched only the
+ * first character of {, } and didn't trust the user).
+ */
+
+ if (!needquotes) {
+ gint len = strlen (config->delimiter);
+ if ((len == 2) && (config->delimiter[1] == ' ')) {
+ needquotes = strchr (value, config->delimiter[0]) ? TRUE : FALSE;
+ if (!needquotes) {
+ len = strlen (config->newline);
+ if ((len == 2) && (config->newline[1] == ' ')) {
+ needquotes = strchr (value, config->newline[0]) ? TRUE : FALSE;
+ if (!needquotes) {
+ len = strlen (config->quote);
+ if ((len == 2) && (config->quote[1] == ' ')) {
+ needquotes = strchr
+ (value, config->quote[0]) ? TRUE : FALSE;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return needquotes;
+}
+
+static GString *
+add_string_to_csv (GString *line,
+ const gchar *value,
+ CsvConfig *config)
+{
+ /* Will add a string to the record and will check for the need for quotes */
+
+ if ((value) && (strlen (value) > 0)) {
+ gboolean needquotes = string_needsquotes (value, config);
+
+ if (needquotes)
+ line = g_string_append (line, config->quote);
+ line = g_string_append (line, (const gchar *) value);
+ if (needquotes)
+ line = g_string_append (line, config->quote);
+ }
+ line = g_string_append (line, config->delimiter);
+ return line;
+}
+
+/* Convert what the user types to what he probably means */
+static gchar *
+userstring_to_systemstring (const gchar *userstring)
+{
+ const gchar *text = userstring;
+ gint i = 0, len = strlen (text);
+ GString *str = g_string_new ("");
+ gchar *retval = NULL;
+
+ while (i < len) {
+ if (text[i] == '\\') {
+ switch (text[i + 1]) {
+ case 'n':
+ str = g_string_append_c (str, '\n');
+ i++;
+ break;
+ case '\\':
+ str = g_string_append_c (str, '\\');
+ i++;
+ break;
+ case 'r':
+ str = g_string_append_c (str, '\r');
+ i++;
+ break;
+ case 't':
+ str = g_string_append_c (str, '\t');
+ i++;
+ break;
+ }
+ } else {
+ str = g_string_append_c (str, text[i]);
+ }
+
+ i++;
+ }
+
+ retval = str->str;
+ g_string_free (str, FALSE);
+
+ return retval;
+}
+
+static void
+do_save_calendar_csv (FormatHandler *handler,
+ ESourceSelector *selector,
+ ECalClientSourceType type,
+ gchar *dest_uri)
+{
+
+ /*
+ * According to some documentation about CSV, newlines 'are' allowed
+ * in CSV-files. But you 'do' have to put the value between quotes.
+ * The helper 'string_needsquotes' will check for that
+ *
+ * http://www.creativyst.com/Doc/Articles/CSV/CSV01.htm
+ * http://www.creativyst.com/cgi-bin/Prod/15/eg/csv2xml.pl
+ */
+
+ ESource *primary_source;
+ EClient *source_client;
+ GError *error = NULL;
+ GSList *objects = NULL;
+ GOutputStream *stream;
+ GString *line = NULL;
+ CsvConfig *config = NULL;
+ CsvPluginData *d = handler->data;
+ const gchar *tmp = NULL;
+
+ if (!dest_uri)
+ return;
+
+ /* open source client */
+ primary_source = e_source_selector_ref_primary_selection (selector);
+ source_client = e_cal_client_connect_sync (
+ primary_source, type, NULL, &error);
+ g_object_unref (primary_source);
+
+ /* Sanity check. */
+ g_return_if_fail (
+ ((source_client != NULL) && (error == NULL)) ||
+ ((source_client == NULL) && (error != NULL)));
+
+ if (source_client == NULL) {
+ display_error_message (
+ gtk_widget_get_toplevel (GTK_WIDGET (selector)),
+ error);
+ g_error_free (error);
+ return;
+ }
+
+ config = g_new (CsvConfig, 1);
+
+ tmp = gtk_entry_get_text (GTK_ENTRY (d->delimiter_entry));
+ config->delimiter = userstring_to_systemstring (tmp ? tmp:", ");
+ tmp = gtk_entry_get_text (GTK_ENTRY (d->newline_entry));
+ config->newline = userstring_to_systemstring (tmp ? tmp:"\\n");
+ tmp = gtk_entry_get_text (GTK_ENTRY (d->quote_entry));
+ config->quote = userstring_to_systemstring (tmp ? tmp:"\"");
+ config->header = gtk_toggle_button_get_active (
+ GTK_TOGGLE_BUTTON (d->header_check));
+
+ stream = open_for_writing (
+ GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (selector))),
+ dest_uri, &error);
+
+ if (stream && e_cal_client_get_object_list_as_comps_sync (E_CAL_CLIENT (source_client), "#t", &objects, NULL, NULL)) {
+ GSList *iter;
+
+ if (config->header) {
+
+ gint i = 0;
+
+ static const gchar *labels[] = {
+ N_("UID"),
+ N_("Summary"),
+ N_("Description List"),
+ N_("Categories List"),
+ N_("Comment List"),
+ N_("Completed"),
+ N_("Created"),
+ N_("Contact List"),
+ N_("Start"),
+ N_("End"),
+ N_("Due"),
+ N_("percent Done"),
+ N_("Priority"),
+ N_("URL"),
+ N_("Attendees List"),
+ N_("Location"),
+ N_("Modified"),
+ };
+
+ line = g_string_new ("");
+ for (i = 0; i < G_N_ELEMENTS (labels); i++) {
+ if (i > 0)
+ g_string_append (line, config->delimiter);
+ g_string_append (line, _(labels[i]));
+ }
+
+ g_string_append (line, config->newline);
+
+ g_output_stream_write_all (
+ stream, line->str, line->len,
+ NULL, NULL, NULL);
+ g_string_free (line, TRUE);
+ }
+
+ for (iter = objects; iter; iter = iter->next) {
+ ECalComponent *comp = iter->data;
+ gchar *delimiter_temp = NULL;
+ const gchar *temp_constchar;
+ GSList *temp_list;
+ ECalComponentDateTime temp_dt;
+ struct icaltimetype *temp_time;
+ gint *temp_int;
+ ECalComponentText temp_comptext;
+
+ line = g_string_new ("");
+
+ /* Getting the stuff */
+ e_cal_component_get_uid (comp, &temp_constchar);
+ line = add_string_to_csv (line, temp_constchar, config);
+
+ e_cal_component_get_summary (comp, &temp_comptext);
+ line = add_string_to_csv (
+ line, temp_comptext.value, config);
+
+ e_cal_component_get_description_list (comp, &temp_list);
+ line = add_list_to_csv (
+ line, temp_list, config, ECALCOMPONENTTEXT);
+ if (temp_list)
+ e_cal_component_free_text_list (temp_list);
+
+ e_cal_component_get_categories_list (comp, &temp_list);
+ line = add_list_to_csv (
+ line, temp_list, config, CONSTCHAR);
+ if (temp_list)
+ e_cal_component_free_categories_list (temp_list);
+
+ e_cal_component_get_comment_list (comp, &temp_list);
+ line = add_list_to_csv (
+ line, temp_list, config, ECALCOMPONENTTEXT);
+ if (temp_list)
+ e_cal_component_free_text_list (temp_list);
+
+ e_cal_component_get_completed (comp, &temp_time);
+ line = add_time_to_csv (line, temp_time, config);
+ if (temp_time)
+ e_cal_component_free_icaltimetype (temp_time);
+
+ e_cal_component_get_created (comp, &temp_time);
+ line = add_time_to_csv (line, temp_time, config);
+ if (temp_time)
+ e_cal_component_free_icaltimetype (temp_time);
+
+ e_cal_component_get_contact_list (comp, &temp_list);
+ line = add_list_to_csv (
+ line, temp_list, config, ECALCOMPONENTTEXT);
+ if (temp_list)
+ e_cal_component_free_text_list (temp_list);
+
+ e_cal_component_get_dtstart (comp, &temp_dt);
+ line = add_time_to_csv (
+ line, temp_dt.value ?
+ temp_dt.value : NULL, config);
+ e_cal_component_free_datetime (&temp_dt);
+
+ e_cal_component_get_dtend (comp, &temp_dt);
+ line = add_time_to_csv (
+ line, temp_dt.value ?
+ temp_dt.value : NULL, config);
+ e_cal_component_free_datetime (&temp_dt);
+
+ e_cal_component_get_due (comp, &temp_dt);
+ line = add_time_to_csv (
+ line, temp_dt.value ?
+ temp_dt.value : NULL, config);
+ e_cal_component_free_datetime (&temp_dt);
+
+ e_cal_component_get_percent (comp, &temp_int);
+ line = add_nummeric_to_csv (line, temp_int, config);
+
+ e_cal_component_get_priority (comp, &temp_int);
+ line = add_nummeric_to_csv (line, temp_int, config);
+
+ e_cal_component_get_url (comp, &temp_constchar);
+ line = add_string_to_csv (line, temp_constchar, config);
+
+ if (e_cal_component_has_attendees (comp)) {
+ e_cal_component_get_attendee_list (comp, &temp_list);
+ line = add_list_to_csv (
+ line, temp_list, config,
+ ECALCOMPONENTATTENDEE);
+ if (temp_list)
+ e_cal_component_free_attendee_list (temp_list);
+ } else {
+ line = add_list_to_csv (
+ line, NULL, config,
+ ECALCOMPONENTATTENDEE);
+ }
+
+ e_cal_component_get_location (comp, &temp_constchar);
+ line = add_string_to_csv (line, temp_constchar, config);
+
+ e_cal_component_get_last_modified (comp, &temp_time);
+
+ /* Append a newline (record delimiter) */
+ delimiter_temp = config->delimiter;
+ config->delimiter = config->newline;
+
+ line = add_time_to_csv (line, temp_time, config);
+
+ /* And restore for the next record */
+ config->delimiter = delimiter_temp;
+
+ /* Important note!
+ * The documentation is not requiring this!
+ *
+ * if (temp_time)
+ * e_cal_component_free_icaltimetype (temp_time);
+ *
+ * Please uncomment and fix documentation if untrue
+ * http://www.gnome.org/projects/evolution/
+ * developer-doc/libecal/ECalComponent.html
+ * #e-cal-component-get-last-modified
+ */
+ g_output_stream_write_all (
+ stream, line->str, line->len,
+ NULL, NULL, &error);
+
+ /* It's written, so we can free it */
+ g_string_free (line, TRUE);
+ }
+
+ g_output_stream_close (stream, NULL, NULL);
+
+ e_cal_client_free_ecalcomp_slist (objects);
+ }
+
+ if (stream)
+ g_object_unref (stream);
+
+ g_object_unref (source_client);
+
+ g_free (config->delimiter);
+ g_free (config->quote);
+ g_free (config->newline);
+ g_free (config);
+
+ if (error) {
+ display_error_message (
+ gtk_widget_get_toplevel (GTK_WIDGET (selector)),
+ error);
+ g_error_free (error);
+ }
+
+ return;
+}
+
+static GtkWidget *
+create_options_widget (FormatHandler *handler)
+{
+ GtkWidget *table = gtk_table_new (4, 2, FALSE), *label = NULL,
+ *csv_options = gtk_expander_new_with_mnemonic (
+ _("A_dvanced options for the CSV format")),
+ *vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
+ CsvPluginData *d = handler->data;
+
+ d->delimiter_entry = gtk_entry_new ();
+ d->newline_entry = gtk_entry_new ();
+ d->quote_entry = gtk_entry_new ();
+ d->header_check = gtk_check_button_new_with_mnemonic (
+ _("Prepend a _header"));
+
+ /* Advanced CSV options */
+ gtk_entry_set_text (GTK_ENTRY (d->delimiter_entry), ", ");
+ gtk_entry_set_text (GTK_ENTRY (d->quote_entry), "\"");
+ gtk_entry_set_text (GTK_ENTRY (d->newline_entry), "\\n");
+
+ gtk_table_set_row_spacings (GTK_TABLE (table), 5);
+ gtk_table_set_col_spacings (GTK_TABLE (table), 5);
+ label = gtk_label_new_with_mnemonic (_("_Value delimiter:"));
+ gtk_misc_set_alignment (GTK_MISC (label), 0, 0.0);
+ gtk_label_set_mnemonic_widget (GTK_LABEL (label), d->delimiter_entry);
+ gtk_table_attach (
+ GTK_TABLE (table), label, 0, 1, 0, 1,
+ (GtkAttachOptions) (GTK_FILL),
+ (GtkAttachOptions) (0), 0, 0);
+ gtk_table_attach (
+ GTK_TABLE (table), d->delimiter_entry, 1, 2, 0, 1,
+ (GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
+ (GtkAttachOptions) (0), 0, 0);
+ label = gtk_label_new_with_mnemonic (_("_Record delimiter:"));
+ gtk_misc_set_alignment (GTK_MISC (label), 0, 0.0);
+ gtk_label_set_mnemonic_widget (GTK_LABEL (label), d->newline_entry);
+ gtk_table_attach (
+ GTK_TABLE (table), label, 0, 1, 1, 2,
+ (GtkAttachOptions) (GTK_FILL),
+ (GtkAttachOptions) (0), 0, 0);
+ gtk_table_attach (
+ GTK_TABLE (table), d->newline_entry, 1, 2, 1, 2,
+ (GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
+ (GtkAttachOptions) (0), 0, 0);
+ label = gtk_label_new_with_mnemonic (_("_Encapsulate values with:"));
+ gtk_misc_set_alignment (GTK_MISC (label), 0, 0.0);
+ gtk_label_set_mnemonic_widget (GTK_LABEL (label), d->quote_entry);
+ gtk_table_attach (
+ GTK_TABLE (table), label, 0, 1, 2, 3,
+ (GtkAttachOptions) (GTK_FILL),
+ (GtkAttachOptions) (0), 0, 0);
+ gtk_table_attach (
+ GTK_TABLE (table), d->quote_entry, 1, 2, 2, 3,
+ (GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
+ (GtkAttachOptions) (0), 0, 0);
+
+ gtk_box_pack_start (GTK_BOX (vbox), d->header_check, TRUE, TRUE, 0);
+ gtk_box_pack_start (GTK_BOX (vbox), table, TRUE, TRUE, 0);
+ gtk_widget_show_all (vbox);
+
+ gtk_container_add (GTK_CONTAINER (csv_options), vbox);
+
+ return csv_options;
+}
+
+FormatHandler *csv_format_handler_new (void)
+{
+ FormatHandler *handler = g_new (FormatHandler, 1);
+
+ handler->isdefault = FALSE;
+ handler->combo_label = _("Comma separated values (.csv)");
+ handler->filename_ext = ".csv";
+ handler->data = g_new (CsvPluginData, 1);
+ handler->options_widget = create_options_widget (handler);
+ handler->save = do_save_calendar_csv;
+
+ return handler;
+}