2
* This program is free software; you can redistribute it and/or
3
* modify it under the terms of the GNU Lesser General Public
4
* License as published by the Free Software Foundation; either
5
* version 2 of the License, or (at your option) version 3.
7
* This program is distributed in the hope that it will be useful,
8
* but WITHOUT ANY WARRANTY; without even the implied warranty of
9
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
10
* Lesser General Public License for more details.
12
* You should have received a copy of the GNU Lesser General Public
13
* License along with the program; if not, see <http://www.gnu.org/licenses/>
17
* Philip Van Hoof <pvanhoof@gnome.org>
19
* Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
28
#include <glib/gi18n.h>
29
#include <libedataserver/e-source.h>
30
#include <libecal/e-cal-time-util.h>
31
#include <libedataserver/e-data-server-util.h>
32
#include <libedataserverui/e-source-selector.h>
33
#include <libedataserverui/e-client-utils.h>
34
#include <libecal/e-cal-client.h>
37
#include "format-handler.h"
39
typedef struct _CsvConfig CsvConfig;
47
static gboolean string_needsquotes (const gchar *value, CsvConfig *config);
49
typedef struct _CsvPluginData CsvPluginData;
52
GtkWidget *delimiter_entry, *newline_entry, *quote_entry, *header_check;
56
display_error_message (GtkWidget *parent, GError *error)
60
dialog = gtk_message_dialog_new (
61
GTK_WINDOW (parent), 0,
62
GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
63
"%s", error->message);
64
gtk_dialog_run (GTK_DIALOG (dialog));
65
gtk_widget_destroy (dialog);
68
enum { /* CSV helper enum */
70
ECALCOMPONENTATTENDEE,
74
/* Some helpers for the csv stuff */
76
add_list_to_csv (GString *line, GSList *list_in, CsvConfig *config, gint type)
80
* This one will write 'ECalComponentText' and 'const char' GSLists. It will
81
* put quotes around the complete written value if there's was only one value
82
* but it required having quotes and if there was more than one value (in which
83
* case delimiters are used to separate them, hence the need for the quotes).
87
gboolean needquotes = FALSE;
88
GSList *list = list_in;
92
const gchar *str = NULL;
94
tmp = g_string_new ("");
98
case ECALCOMPONENTATTENDEE:
99
str = ((ECalComponentAttendee*) list->data)->value;
101
case ECALCOMPONENTTEXT:
102
str = ((ECalComponentText*) list->data)->value;
110
needquotes = string_needsquotes (str, config);
112
tmp = g_string_append (tmp, (const gchar *) str);
113
list = g_slist_next (list); cnt++;
115
tmp = g_string_append (tmp, config->delimiter);
119
line = g_string_append (line, config->quote);
120
line = g_string_append_len (line, tmp->str, tmp->len);
121
g_string_free (tmp, TRUE);
123
line = g_string_append (line, config->quote);
126
line = g_string_append (line, config->delimiter);
131
add_nummeric_to_csv (GString *line, gint *nummeric, CsvConfig *config)
135
* This one will write {-1}..{00}..{01}..{99}
136
* it prepends a 0 if it's < 10 and > -1
140
g_string_append_printf (
142
(*nummeric < 10 && *nummeric > -1) ? "0" : "",
145
return g_string_append (line, config->delimiter);
149
add_time_to_csv (GString *line, icaltimetype *time, CsvConfig *config)
153
gboolean needquotes = FALSE;
154
struct tm mytm = icaltimetype_to_tm (time);
155
gchar *str = (gchar *) g_malloc (sizeof (gchar) * 200);
157
/* Translators: the %F %T is the third argument for a
158
* strftime function. It lets you define the formatting
159
* of the date in the csv-file. */
160
e_utf8_strftime (str, 200, _("%F %T"), &mytm);
162
needquotes = string_needsquotes (str, config);
165
line = g_string_append (line, config->quote);
167
line = g_string_append (line, str);
170
line = g_string_append (line, config->quote);
176
line = g_string_append (line, config->delimiter);
182
string_needsquotes (const gchar *value, CsvConfig *config)
185
/* This is the actual need for quotes-checker */
188
* These are the simple substring-checks
190
* Example: {Mom, can you please do that for me?}
191
* Will be written as {"Mom, can you please do that for me?"}
194
gboolean needquotes = strstr (value, config->delimiter) ? TRUE:FALSE;
197
needquotes = strstr (value, config->newline) ? TRUE:FALSE;
199
needquotes = strstr (value, config->quote) ? TRUE:FALSE;
203
* If the special-char is char+onespace (so like {, } {" }, {\n }) and it occurs
204
* the value that is going to be written
206
* In this case we don't trust the user . . . and are going to quote the string
207
* just to play save -- Quoting is always allowed in the CSV format. If you can
208
* avoid it, it's better to do so since a lot applications don't support CSV
211
* Example: {Mom,can you please do that for me?}
212
* This example will be written as {"Mom,can you please do that for me?"} because
213
* there's a {,} behind {Mom} and the delimiter is {, } (so we searched only the
214
* first character of {, } and didn't trust the user).
218
gint len = strlen (config->delimiter);
219
if ((len == 2) && (config->delimiter[1] == ' ')) {
220
needquotes = strchr (value, config->delimiter[0])?TRUE:FALSE;
222
len = strlen (config->newline);
223
if ((len == 2) && (config->newline[1] == ' ')) {
224
needquotes = strchr (value, config->newline[0])?TRUE:FALSE;
226
len = strlen (config->quote);
227
if ((len == 2) && (config->quote[1] == ' ')) {
229
(value, config->quote[0])?TRUE:FALSE;
241
add_string_to_csv (GString *line, const gchar *value, CsvConfig *config)
243
/* Will add a string to the record and will check for the need for quotes */
245
if ((value) && (strlen (value)>0)) {
246
gboolean needquotes = string_needsquotes (value, config);
249
line = g_string_append (line, config->quote);
250
line = g_string_append (line, (const gchar *) value);
252
line = g_string_append (line, config->quote);
254
line = g_string_append (line, config->delimiter);
258
/* Convert what the user types to what he probably means */
260
userstring_to_systemstring (const gchar *userstring)
262
const gchar *text = userstring;
263
gint i=0, len = strlen (text);
264
GString *str = g_string_new ("");
265
gchar *retval = NULL;
268
if (text[i] == '\\') {
271
str = g_string_append_c (str, '\n');
275
str = g_string_append_c (str, '\\');
279
str = g_string_append_c (str, '\r');
283
str = g_string_append_c (str, '\t');
288
str = g_string_append_c (str, text[i]);
295
g_string_free (str, FALSE);
301
do_save_calendar_csv (FormatHandler *handler,
302
ESourceSelector *selector,
303
ECalClientSourceType type,
308
* According to some documentation about CSV, newlines 'are' allowed
309
* in CSV-files. But you 'do' have to put the value between quotes.
310
* The helper 'string_needsquotes' will check for that
312
* http://www.creativyst.com/Doc/Articles/CSV/CSV01.htm
313
* http://www.creativyst.com/cgi-bin/Prod/15/eg/csv2xml.pl
316
ESource *primary_source;
317
ECalClient *source_client;
318
GError *error = NULL;
319
GSList *objects = NULL;
320
GOutputStream *stream;
321
GString *line = NULL;
322
CsvConfig *config = NULL;
323
CsvPluginData *d = handler->data;
324
const gchar *tmp = NULL;
329
primary_source = e_source_selector_get_primary_selection (selector);
331
/* open source client */
332
source_client = e_cal_client_new (primary_source, type, &error);
334
g_signal_connect (source_client, "authenticate", G_CALLBACK (e_client_utils_authenticate_handler), NULL);
336
if (!source_client || !e_client_open_sync (E_CLIENT (source_client), TRUE, NULL, &error)) {
337
display_error_message (
338
gtk_widget_get_toplevel (GTK_WIDGET (selector)),
341
g_object_unref (source_client);
342
g_error_free (error);
346
config = g_new (CsvConfig, 1);
348
tmp = gtk_entry_get_text (GTK_ENTRY (d->delimiter_entry));
349
config->delimiter = userstring_to_systemstring (tmp?tmp:", ");
350
tmp = gtk_entry_get_text (GTK_ENTRY (d->newline_entry));
351
config->newline = userstring_to_systemstring (tmp?tmp:"\\n");
352
tmp = gtk_entry_get_text (GTK_ENTRY (d->quote_entry));
353
config->quote = userstring_to_systemstring (tmp?tmp:"\"");
354
config->header = gtk_toggle_button_get_active (
355
GTK_TOGGLE_BUTTON (d->header_check));
357
stream = open_for_writing (
358
GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (selector))),
361
if (stream && e_cal_client_get_object_list_as_comps_sync (source_client, "#t", &objects, NULL, NULL)) {
364
if (config->header) {
368
static const gchar *labels[] = {
371
N_("Description List"),
372
N_("Categories List"),
383
N_("Attendees List"),
388
line = g_string_new ("");
389
for (i=0;i<G_N_ELEMENTS (labels);i++) {
391
g_string_append (line, config->delimiter);
392
g_string_append (line, _(labels[i]));
395
g_string_append (line, config->newline);
397
g_output_stream_write_all (
398
stream, line->str, line->len,
400
g_string_free (line, TRUE);
403
for (iter = objects; iter; iter = iter->next) {
404
ECalComponent *comp = objects->data;
405
gchar *delimiter_temp = NULL;
406
const gchar *temp_constchar;
408
ECalComponentDateTime temp_dt;
409
struct icaltimetype *temp_time;
411
ECalComponentText temp_comptext;
413
line = g_string_new ("");
415
/* Getting the stuff */
416
e_cal_component_get_uid (comp, &temp_constchar);
417
line = add_string_to_csv (line, temp_constchar, config);
419
e_cal_component_get_summary (comp, &temp_comptext);
420
line = add_string_to_csv (
421
line, temp_comptext.value, config);
423
e_cal_component_get_description_list (comp, &temp_list);
424
line = add_list_to_csv (
425
line, temp_list, config, ECALCOMPONENTTEXT);
427
e_cal_component_free_text_list (temp_list);
429
e_cal_component_get_categories_list (comp, &temp_list);
430
line = add_list_to_csv (
431
line, temp_list, config, CONSTCHAR);
433
e_cal_component_free_categories_list (temp_list);
435
e_cal_component_get_comment_list (comp, &temp_list);
436
line = add_list_to_csv (
437
line, temp_list, config, ECALCOMPONENTTEXT);
439
e_cal_component_free_text_list (temp_list);
441
e_cal_component_get_completed (comp, &temp_time);
442
line = add_time_to_csv (line, temp_time, config);
444
e_cal_component_free_icaltimetype (temp_time);
446
e_cal_component_get_created (comp, &temp_time);
447
line = add_time_to_csv (line, temp_time, config);
449
e_cal_component_free_icaltimetype (temp_time);
451
e_cal_component_get_contact_list (comp, &temp_list);
452
line = add_list_to_csv (
453
line, temp_list, config, ECALCOMPONENTTEXT);
455
e_cal_component_free_text_list (temp_list);
457
e_cal_component_get_dtstart (comp, &temp_dt);
458
line = add_time_to_csv (
459
line, temp_dt.value ?
460
temp_dt.value : NULL, config);
461
e_cal_component_free_datetime (&temp_dt);
463
e_cal_component_get_dtend (comp, &temp_dt);
464
line = add_time_to_csv (
465
line, temp_dt.value ?
466
temp_dt.value : NULL, config);
467
e_cal_component_free_datetime (&temp_dt);
469
e_cal_component_get_due (comp, &temp_dt);
470
line = add_time_to_csv (
471
line, temp_dt.value ?
472
temp_dt.value : NULL, config);
473
e_cal_component_free_datetime (&temp_dt);
475
e_cal_component_get_percent (comp, &temp_int);
476
line = add_nummeric_to_csv (line, temp_int, config);
478
e_cal_component_get_priority (comp, &temp_int);
479
line = add_nummeric_to_csv (line, temp_int, config);
481
e_cal_component_get_url (comp, &temp_constchar);
482
line = add_string_to_csv (line, temp_constchar, config);
484
if (e_cal_component_has_attendees (comp)) {
485
e_cal_component_get_attendee_list (comp, &temp_list);
486
line = add_list_to_csv (
487
line, temp_list, config,
488
ECALCOMPONENTATTENDEE);
490
e_cal_component_free_attendee_list (temp_list);
492
line = add_list_to_csv (
494
ECALCOMPONENTATTENDEE);
497
e_cal_component_get_location (comp, &temp_constchar);
498
line = add_string_to_csv (line, temp_constchar, config);
500
e_cal_component_get_last_modified (comp, &temp_time);
502
/* Append a newline (record delimiter) */
503
delimiter_temp = config->delimiter;
504
config->delimiter = config->newline;
506
line = add_time_to_csv (line, temp_time, config);
508
/* And restore for the next record */
509
config->delimiter = delimiter_temp;
512
* The documentation is not requiring this!
515
* e_cal_component_free_icaltimetype (temp_time);
517
* Please uncomment and fix documentation if untrue
518
* http://www.gnome.org/projects/evolution/
519
* developer-doc/libecal/ECalComponent.html
520
* #e-cal-component-get-last-modified
522
g_output_stream_write_all (
523
stream, line->str, line->len,
526
/* It's written, so we can free it */
527
g_string_free (line, TRUE);
530
g_output_stream_close (stream, NULL, NULL);
532
e_cal_client_free_ecalcomp_slist (objects);
536
g_object_unref (stream);
538
g_object_unref (source_client);
540
g_free (config->delimiter);
541
g_free (config->quote);
542
g_free (config->newline);
546
display_error_message (
547
gtk_widget_get_toplevel (GTK_WIDGET (selector)),
549
g_error_free (error);
556
create_options_widget (FormatHandler *handler)
558
GtkWidget *table = gtk_table_new (4, 2, FALSE), *label = NULL,
559
*csv_options = gtk_expander_new_with_mnemonic (
560
_("A_dvanced options for the CSV format")),
561
*vbox = gtk_vbox_new (FALSE, 0);
562
CsvPluginData *d = handler->data;
564
d->delimiter_entry = gtk_entry_new ();
565
d->newline_entry = gtk_entry_new ();
566
d->quote_entry = gtk_entry_new ();
567
d->header_check = gtk_check_button_new_with_mnemonic (
568
_("Prepend a _header"));
570
/* Advanced CSV options */
571
gtk_entry_set_text (GTK_ENTRY(d->delimiter_entry), ", ");
572
gtk_entry_set_text (GTK_ENTRY(d->quote_entry), "\"");
573
gtk_entry_set_text (GTK_ENTRY(d->newline_entry), "\\n");
575
gtk_table_set_row_spacings (GTK_TABLE (table), 5);
576
gtk_table_set_col_spacings (GTK_TABLE (table), 5);
577
label = gtk_label_new_with_mnemonic (_("_Value delimiter:"));
578
gtk_misc_set_alignment (GTK_MISC (label), 0, 0.0);
579
gtk_label_set_mnemonic_widget (GTK_LABEL (label), d->delimiter_entry);
581
GTK_TABLE (table), label, 0, 1, 0, 1,
582
(GtkAttachOptions) (GTK_FILL),
583
(GtkAttachOptions) (0), 0, 0);
585
GTK_TABLE (table), d->delimiter_entry, 1, 2, 0, 1,
586
(GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
587
(GtkAttachOptions) (0), 0, 0);
588
label = gtk_label_new_with_mnemonic (_("_Record delimiter:"));
589
gtk_misc_set_alignment (GTK_MISC (label), 0, 0.0);
590
gtk_label_set_mnemonic_widget (GTK_LABEL (label), d->newline_entry);
592
GTK_TABLE (table), label, 0, 1, 1, 2,
593
(GtkAttachOptions) (GTK_FILL),
594
(GtkAttachOptions) (0), 0, 0);
596
GTK_TABLE (table), d->newline_entry, 1, 2, 1, 2,
597
(GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
598
(GtkAttachOptions) (0), 0, 0);
599
label = gtk_label_new_with_mnemonic (_("_Encapsulate values with:"));
600
gtk_misc_set_alignment (GTK_MISC (label), 0, 0.0);
601
gtk_label_set_mnemonic_widget (GTK_LABEL (label), d->quote_entry);
603
GTK_TABLE (table), label, 0, 1, 2, 3,
604
(GtkAttachOptions) (GTK_FILL),
605
(GtkAttachOptions) (0), 0, 0);
607
GTK_TABLE (table), d->quote_entry, 1, 2, 2, 3,
608
(GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
609
(GtkAttachOptions) (0), 0, 0);
611
gtk_box_pack_start (GTK_BOX (vbox), d->header_check, TRUE, TRUE, 0);
612
gtk_box_pack_start (GTK_BOX (vbox), table, TRUE, TRUE, 0);
613
gtk_widget_show_all (vbox);
615
gtk_container_add (GTK_CONTAINER (csv_options), vbox);
620
FormatHandler *csv_format_handler_new (void)
622
FormatHandler *handler = g_new (FormatHandler, 1);
624
handler->isdefault = FALSE;
625
handler->combo_label = _("Comma separated value format (.csv)");
626
handler->filename_ext = ".csv";
627
handler->data = g_new (CsvPluginData, 1);
628
handler->options_widget = create_options_widget (handler);
629
handler->save = do_save_calendar_csv;