/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
*
* Authors: Philip Van Hoof <pvanhoof@gnome.org>
*
* Copyright 2004 Novell, Inc. (www.novell.com)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of version 2 of the GNU General Public
* License as published by the Free Software Foundation.
*
* 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <glib.h>
#include <glib/gi18n.h>
#include <gtk/gtkfilechooser.h>
#include <gtk/gtkfilechooserdialog.h>
#include <gtk/gtkmessagedialog.h>
#include <gtk/gtkstock.h>
#include <gtk/gtk.h>
#include <libedataserver/e-source.h>
#include <libecal/e-cal-time-util.h>
#include <libedataserver/e-data-server-util.h>
#include <libedataserverui/e-source-selector.h>
#include <libecal/e-cal.h>
#include "calendar/common/authentication.h"
#include <calendar/gui/e-cal-popup.h>
#include <libgnomevfs/gnome-vfs.h>
#include <string.h>
#include "e-util/e-error.h"
#include "format-handler.h"
typedef struct _CsvConfig CsvConfig;
struct _CsvConfig {
gchar *newline;
gchar *quote;
gchar *delimiter;
gboolean header;
};
static gboolean string_needsquotes (const char *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 char *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);
line = g_string_append (line, config->delimiter);
return line;
}
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);
/*
* Translator: the %F %T is the thirth 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 char *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 char *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, EPlugin *ep, ECalPopupTargetSource *target, ECalSourceType type, char *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;
ECal *source_client;
GError *error = NULL;
GList *objects=NULL;
GnomeVFSResult result;
GnomeVFSHandle *handle;
GnomeVFSURI *uri;
GString *line = NULL;
CsvConfig *config = NULL;
CsvPluginData *d = handler->data;
const gchar *tmp = NULL;
gboolean doit = TRUE;
if (!dest_uri)
return;
primary_source = e_source_selector_peek_primary_selection (target->selector);
/* open source client */
source_client = auth_new_cal_from_source (primary_source, type);
if (!e_cal_open (source_client, TRUE, &error)) {
display_error_message (gtk_widget_get_toplevel (GTK_WIDGET (target->selector)), error);
g_object_unref (source_client);
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));
uri = gnome_vfs_uri_new (dest_uri);
result = gnome_vfs_open_uri (&handle, uri, GNOME_VFS_OPEN_READ);
if (result == GNOME_VFS_OK)
doit = e_error_run(GTK_WINDOW(gtk_widget_get_toplevel (GTK_WIDGET (target->selector))),
E_ERROR_ASK_FILE_EXISTS_OVERWRITE, dest_uri, NULL) == GTK_RESPONSE_OK;
if (doit) {
result = gnome_vfs_open_uri (&handle, uri, GNOME_VFS_OPEN_WRITE);
if (result != GNOME_VFS_OK) {
gnome_vfs_create (&handle, dest_uri, GNOME_VFS_OPEN_WRITE, TRUE, GNOME_VFS_PERM_USER_ALL);
result = gnome_vfs_open_uri (&handle, uri, GNOME_VFS_OPEN_WRITE);
}
}
if (result == GNOME_VFS_OK && doit && e_cal_get_object_list_as_comp (source_client, "#t", &objects, NULL)) {
if (config->header) {
gint i=0;
static 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)
line = g_string_append(line, config->delimiter);
line = g_string_append(line, _(labels[i]));
}
line = g_string_append (line, config->newline);
gnome_vfs_write (handle, line->str, line->len, NULL);
g_string_free (line, TRUE);
}
while (objects != NULL) {
ECalComponent *comp = objects->data;
gchar *delimiter_temp = NULL;
const char *temp_constchar;
GSList *temp_list;
ECalComponentDateTime temp_dt;
struct icaltimetype *temp_time;
int *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?temp_comptext.value:NULL, 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
*/
gnome_vfs_write (handle, line->str, line->len, NULL);
/* It's written, so we can free it */
g_string_free (line, TRUE);
objects = g_list_next (objects);
}
gnome_vfs_close (handle);
}
g_object_unref (source_client);
g_free (config->delimiter);
g_free (config->quote);
g_free (config->newline);
g_free (config);
return;
}
static GtkWidget *
create_options_widget (FormatHandler *handler)
{
GtkWidget *table = gtk_table_new (4, 2, FALSE), *label = NULL,
*csv_options = gtk_expander_new (_("Advanced options for the CSV format")),
*vbox = gtk_vbox_new (FALSE, 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_label (_("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 (_("Value delimiter:"));
gtk_misc_set_alignment (GTK_MISC (label), 0, 0.0);
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 (_("Record delimiter:"));
gtk_misc_set_alignment (GTK_MISC (label), 0, 0.0);
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 (_("Encapsulate values with:"));
gtk_misc_set_alignment (GTK_MISC (label), 0, 0.0);
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 value format (.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;
}