No issues found
1 /*
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.
6 *
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.
11 *
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/>
14 *
15 *
16 * Authors:
17 * Philip Van Hoof <pvanhoof@gnome.org>
18 *
19 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
20 *
21 */
22
23 #ifdef HAVE_CONFIG_H
24 #include <config.h>
25 #endif
26
27 #include <string.h>
28 #include <glib/gi18n.h>
29
30 #include "format-handler.h"
31
32 typedef struct _CsvConfig CsvConfig;
33 struct _CsvConfig {
34 gchar *newline;
35 gchar *quote;
36 gchar *delimiter;
37 gboolean header;
38 };
39
40 static gboolean string_needsquotes (const gchar *value, CsvConfig *config);
41
42 typedef struct _CsvPluginData CsvPluginData;
43 struct _CsvPluginData
44 {
45 GtkWidget *delimiter_entry, *newline_entry, *quote_entry, *header_check;
46 };
47
48 static void
49 display_error_message (GtkWidget *parent,
50 GError *error)
51 {
52 GtkWidget *dialog;
53
54 dialog = gtk_message_dialog_new (
55 GTK_WINDOW (parent), 0,
56 GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
57 "%s", error->message);
58 gtk_dialog_run (GTK_DIALOG (dialog));
59 gtk_widget_destroy (dialog);
60 }
61
62 enum { /* CSV helper enum */
63 ECALCOMPONENTTEXT,
64 ECALCOMPONENTATTENDEE,
65 CONSTCHAR
66 };
67
68 /* Some helpers for the csv stuff */
69 static GString *
70 add_list_to_csv (GString *line,
71 GSList *list_in,
72 CsvConfig *config,
73 gint type)
74 {
75
76 /*
77 * This one will write 'ECalComponentText' and 'const char' GSLists. It will
78 * put quotes around the complete written value if there's was only one value
79 * but it required having quotes and if there was more than one value (in which
80 * case delimiters are used to separate them, hence the need for the quotes).
81 */
82
83 if (list_in) {
84 gboolean needquotes = FALSE;
85 GSList *list = list_in;
86 GString *tmp = NULL;
87 gint cnt = 0;
88 while (list) {
89 const gchar *str = NULL;
90 if (cnt == 0)
91 tmp = g_string_new ("");
92 if (cnt > 0)
93 needquotes = TRUE;
94 switch (type) {
95 case ECALCOMPONENTATTENDEE:
96 str = ((ECalComponentAttendee *) list->data)->value;
97 break;
98 case ECALCOMPONENTTEXT:
99 str = ((ECalComponentText *) list->data)->value;
100 break;
101 case CONSTCHAR:
102 default:
103 str = list->data;
104 break;
105 }
106 if (!needquotes)
107 needquotes = string_needsquotes (str, config);
108 if (str)
109 tmp = g_string_append (tmp, (const gchar *) str);
110 list = g_slist_next (list); cnt++;
111 if (list)
112 tmp = g_string_append (tmp, config->delimiter);
113 }
114
115 if (needquotes)
116 line = g_string_append (line, config->quote);
117 line = g_string_append_len (line, tmp->str, tmp->len);
118 g_string_free (tmp, TRUE);
119 if (needquotes)
120 line = g_string_append (line, config->quote);
121 }
122
123 line = g_string_append (line, config->delimiter);
124 return line;
125 }
126
127 static GString *
128 add_nummeric_to_csv (GString *line,
129 gint *nummeric,
130 CsvConfig *config)
131 {
132
133 /*
134 * This one will write {-1}..{00}..{01}..{99}
135 * it prepends a 0 if it's < 10 and > -1
136 */
137
138 if (nummeric)
139 g_string_append_printf (
140 line, "%s%d",
141 (*nummeric < 10 && *nummeric > -1) ? "0" : "",
142 *nummeric);
143
144 return g_string_append (line, config->delimiter);
145 }
146
147 static GString *
148 add_time_to_csv (GString *line,
149 icaltimetype *time,
150 CsvConfig *config)
151 {
152
153 if (time) {
154 gboolean needquotes = FALSE;
155 struct tm mytm = icaltimetype_to_tm (time);
156 gchar *str = (gchar *) g_malloc (sizeof (gchar) * 200);
157
158 /* Translators: the %F %T is the third argument for a
159 * strftime function. It lets you define the formatting
160 * of the date in the csv-file. */
161 e_utf8_strftime (str, 200, _("%F %T"), &mytm);
162
163 needquotes = string_needsquotes (str, config);
164
165 if (needquotes)
166 line = g_string_append (line, config->quote);
167
168 line = g_string_append (line, str);
169
170 if (needquotes)
171 line = g_string_append (line, config->quote);
172
173 g_free (str);
174
175 }
176
177 line = g_string_append (line, config->delimiter);
178
179 return line;
180 }
181
182 static gboolean
183 string_needsquotes (const gchar *value,
184 CsvConfig *config)
185 {
186
187 /* This is the actual need for quotes-checker */
188
189 /*
190 * These are the simple substring-checks
191 *
192 * Example: {Mom, can you please do that for me?}
193 * Will be written as {"Mom, can you please do that for me?"}
194 */
195
196 gboolean needquotes = strstr (value, config->delimiter) ? TRUE : FALSE;
197
198 if (!needquotes) {
199 needquotes = strstr (value, config->newline) ? TRUE : FALSE;
200 if (!needquotes)
201 needquotes = strstr (value, config->quote) ? TRUE : FALSE;
202 }
203
204 /*
205 * If the special-char is char+onespace (so like {, } {" }, {\n }) and it occurs
206 * the value that is going to be written
207 *
208 * In this case we don't trust the user . . . and are going to quote the string
209 * just to play save -- Quoting is always allowed in the CSV format. If you can
210 * avoid it, it's better to do so since a lot applications don't support CSV
211 * correctly! --.
212 *
213 * Example: {Mom,can you please do that for me?}
214 * This example will be written as {"Mom,can you please do that for me?"} because
215 * there's a {,} behind {Mom} and the delimiter is {, } (so we searched only the
216 * first character of {, } and didn't trust the user).
217 */
218
219 if (!needquotes) {
220 gint len = strlen (config->delimiter);
221 if ((len == 2) && (config->delimiter[1] == ' ')) {
222 needquotes = strchr (value, config->delimiter[0]) ? TRUE : FALSE;
223 if (!needquotes) {
224 len = strlen (config->newline);
225 if ((len == 2) && (config->newline[1] == ' ')) {
226 needquotes = strchr (value, config->newline[0]) ? TRUE : FALSE;
227 if (!needquotes) {
228 len = strlen (config->quote);
229 if ((len == 2) && (config->quote[1] == ' ')) {
230 needquotes = strchr
231 (value, config->quote[0]) ? TRUE : FALSE;
232 }
233 }
234 }
235 }
236 }
237 }
238
239 return needquotes;
240 }
241
242 static GString *
243 add_string_to_csv (GString *line,
244 const gchar *value,
245 CsvConfig *config)
246 {
247 /* Will add a string to the record and will check for the need for quotes */
248
249 if ((value) && (strlen (value) > 0)) {
250 gboolean needquotes = string_needsquotes (value, config);
251
252 if (needquotes)
253 line = g_string_append (line, config->quote);
254 line = g_string_append (line, (const gchar *) value);
255 if (needquotes)
256 line = g_string_append (line, config->quote);
257 }
258 line = g_string_append (line, config->delimiter);
259 return line;
260 }
261
262 /* Convert what the user types to what he probably means */
263 static gchar *
264 userstring_to_systemstring (const gchar *userstring)
265 {
266 const gchar *text = userstring;
267 gint i = 0, len = strlen (text);
268 GString *str = g_string_new ("");
269 gchar *retval = NULL;
270
271 while (i < len) {
272 if (text[i] == '\\') {
273 switch (text[i + 1]) {
274 case 'n':
275 str = g_string_append_c (str, '\n');
276 i++;
277 break;
278 case '\\':
279 str = g_string_append_c (str, '\\');
280 i++;
281 break;
282 case 'r':
283 str = g_string_append_c (str, '\r');
284 i++;
285 break;
286 case 't':
287 str = g_string_append_c (str, '\t');
288 i++;
289 break;
290 }
291 } else {
292 str = g_string_append_c (str, text[i]);
293 }
294
295 i++;
296 }
297
298 retval = str->str;
299 g_string_free (str, FALSE);
300
301 return retval;
302 }
303
304 static void
305 do_save_calendar_csv (FormatHandler *handler,
306 ESourceSelector *selector,
307 ECalClientSourceType type,
308 gchar *dest_uri)
309 {
310
311 /*
312 * According to some documentation about CSV, newlines 'are' allowed
313 * in CSV-files. But you 'do' have to put the value between quotes.
314 * The helper 'string_needsquotes' will check for that
315 *
316 * http://www.creativyst.com/Doc/Articles/CSV/CSV01.htm
317 * http://www.creativyst.com/cgi-bin/Prod/15/eg/csv2xml.pl
318 */
319
320 ESource *primary_source;
321 ECalClient *source_client;
322 GError *error = NULL;
323 GSList *objects = NULL;
324 GOutputStream *stream;
325 GString *line = NULL;
326 CsvConfig *config = NULL;
327 CsvPluginData *d = handler->data;
328 const gchar *tmp = NULL;
329
330 if (!dest_uri)
331 return;
332
333 /* open source client */
334 primary_source = e_source_selector_ref_primary_selection (selector);
335 source_client = e_cal_client_new (primary_source, type, &error);
336 g_object_unref (primary_source);
337
338 if (!source_client || !e_client_open_sync (E_CLIENT (source_client), TRUE, NULL, &error)) {
339 display_error_message (
340 gtk_widget_get_toplevel (GTK_WIDGET (selector)),
341 error);
342 if (source_client)
343 g_object_unref (source_client);
344 g_error_free (error);
345 return;
346 }
347
348 config = g_new (CsvConfig, 1);
349
350 tmp = gtk_entry_get_text (GTK_ENTRY (d->delimiter_entry));
351 config->delimiter = userstring_to_systemstring (tmp ? tmp:", ");
352 tmp = gtk_entry_get_text (GTK_ENTRY (d->newline_entry));
353 config->newline = userstring_to_systemstring (tmp ? tmp:"\\n");
354 tmp = gtk_entry_get_text (GTK_ENTRY (d->quote_entry));
355 config->quote = userstring_to_systemstring (tmp ? tmp:"\"");
356 config->header = gtk_toggle_button_get_active (
357 GTK_TOGGLE_BUTTON (d->header_check));
358
359 stream = open_for_writing (
360 GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (selector))),
361 dest_uri, &error);
362
363 if (stream && e_cal_client_get_object_list_as_comps_sync (source_client, "#t", &objects, NULL, NULL)) {
364 GSList *iter;
365
366 if (config->header) {
367
368 gint i = 0;
369
370 static const gchar *labels[] = {
371 N_("UID"),
372 N_("Summary"),
373 N_("Description List"),
374 N_("Categories List"),
375 N_("Comment List"),
376 N_("Completed"),
377 N_("Created"),
378 N_("Contact List"),
379 N_("Start"),
380 N_("End"),
381 N_("Due"),
382 N_("percent Done"),
383 N_("Priority"),
384 N_("URL"),
385 N_("Attendees List"),
386 N_("Location"),
387 N_("Modified"),
388 };
389
390 line = g_string_new ("");
391 for (i = 0; i < G_N_ELEMENTS (labels); i++) {
392 if (i > 0)
393 g_string_append (line, config->delimiter);
394 g_string_append (line, _(labels[i]));
395 }
396
397 g_string_append (line, config->newline);
398
399 g_output_stream_write_all (
400 stream, line->str, line->len,
401 NULL, NULL, NULL);
402 g_string_free (line, TRUE);
403 }
404
405 for (iter = objects; iter; iter = iter->next) {
406 ECalComponent *comp = iter->data;
407 gchar *delimiter_temp = NULL;
408 const gchar *temp_constchar;
409 GSList *temp_list;
410 ECalComponentDateTime temp_dt;
411 struct icaltimetype *temp_time;
412 gint *temp_int;
413 ECalComponentText temp_comptext;
414
415 line = g_string_new ("");
416
417 /* Getting the stuff */
418 e_cal_component_get_uid (comp, &temp_constchar);
419 line = add_string_to_csv (line, temp_constchar, config);
420
421 e_cal_component_get_summary (comp, &temp_comptext);
422 line = add_string_to_csv (
423 line, temp_comptext.value, config);
424
425 e_cal_component_get_description_list (comp, &temp_list);
426 line = add_list_to_csv (
427 line, temp_list, config, ECALCOMPONENTTEXT);
428 if (temp_list)
429 e_cal_component_free_text_list (temp_list);
430
431 e_cal_component_get_categories_list (comp, &temp_list);
432 line = add_list_to_csv (
433 line, temp_list, config, CONSTCHAR);
434 if (temp_list)
435 e_cal_component_free_categories_list (temp_list);
436
437 e_cal_component_get_comment_list (comp, &temp_list);
438 line = add_list_to_csv (
439 line, temp_list, config, ECALCOMPONENTTEXT);
440 if (temp_list)
441 e_cal_component_free_text_list (temp_list);
442
443 e_cal_component_get_completed (comp, &temp_time);
444 line = add_time_to_csv (line, temp_time, config);
445 if (temp_time)
446 e_cal_component_free_icaltimetype (temp_time);
447
448 e_cal_component_get_created (comp, &temp_time);
449 line = add_time_to_csv (line, temp_time, config);
450 if (temp_time)
451 e_cal_component_free_icaltimetype (temp_time);
452
453 e_cal_component_get_contact_list (comp, &temp_list);
454 line = add_list_to_csv (
455 line, temp_list, config, ECALCOMPONENTTEXT);
456 if (temp_list)
457 e_cal_component_free_text_list (temp_list);
458
459 e_cal_component_get_dtstart (comp, &temp_dt);
460 line = add_time_to_csv (
461 line, temp_dt.value ?
462 temp_dt.value : NULL, config);
463 e_cal_component_free_datetime (&temp_dt);
464
465 e_cal_component_get_dtend (comp, &temp_dt);
466 line = add_time_to_csv (
467 line, temp_dt.value ?
468 temp_dt.value : NULL, config);
469 e_cal_component_free_datetime (&temp_dt);
470
471 e_cal_component_get_due (comp, &temp_dt);
472 line = add_time_to_csv (
473 line, temp_dt.value ?
474 temp_dt.value : NULL, config);
475 e_cal_component_free_datetime (&temp_dt);
476
477 e_cal_component_get_percent (comp, &temp_int);
478 line = add_nummeric_to_csv (line, temp_int, config);
479
480 e_cal_component_get_priority (comp, &temp_int);
481 line = add_nummeric_to_csv (line, temp_int, config);
482
483 e_cal_component_get_url (comp, &temp_constchar);
484 line = add_string_to_csv (line, temp_constchar, config);
485
486 if (e_cal_component_has_attendees (comp)) {
487 e_cal_component_get_attendee_list (comp, &temp_list);
488 line = add_list_to_csv (
489 line, temp_list, config,
490 ECALCOMPONENTATTENDEE);
491 if (temp_list)
492 e_cal_component_free_attendee_list (temp_list);
493 } else {
494 line = add_list_to_csv (
495 line, NULL, config,
496 ECALCOMPONENTATTENDEE);
497 }
498
499 e_cal_component_get_location (comp, &temp_constchar);
500 line = add_string_to_csv (line, temp_constchar, config);
501
502 e_cal_component_get_last_modified (comp, &temp_time);
503
504 /* Append a newline (record delimiter) */
505 delimiter_temp = config->delimiter;
506 config->delimiter = config->newline;
507
508 line = add_time_to_csv (line, temp_time, config);
509
510 /* And restore for the next record */
511 config->delimiter = delimiter_temp;
512
513 /* Important note!
514 * The documentation is not requiring this!
515 *
516 * if (temp_time)
517 * e_cal_component_free_icaltimetype (temp_time);
518 *
519 * Please uncomment and fix documentation if untrue
520 * http://www.gnome.org/projects/evolution/
521 * developer-doc/libecal/ECalComponent.html
522 * #e-cal-component-get-last-modified
523 */
524 g_output_stream_write_all (
525 stream, line->str, line->len,
526 NULL, NULL, &error);
527
528 /* It's written, so we can free it */
529 g_string_free (line, TRUE);
530 }
531
532 g_output_stream_close (stream, NULL, NULL);
533
534 e_cal_client_free_ecalcomp_slist (objects);
535 }
536
537 if (stream)
538 g_object_unref (stream);
539
540 g_object_unref (source_client);
541
542 g_free (config->delimiter);
543 g_free (config->quote);
544 g_free (config->newline);
545 g_free (config);
546
547 if (error) {
548 display_error_message (
549 gtk_widget_get_toplevel (GTK_WIDGET (selector)),
550 error);
551 g_error_free (error);
552 }
553
554 return;
555 }
556
557 static GtkWidget *
558 create_options_widget (FormatHandler *handler)
559 {
560 GtkWidget *table = gtk_table_new (4, 2, FALSE), *label = NULL,
561 *csv_options = gtk_expander_new_with_mnemonic (
562 _("A_dvanced options for the CSV format")),
563 *vbox = gtk_vbox_new (FALSE, 0);
564 CsvPluginData *d = handler->data;
565
566 d->delimiter_entry = gtk_entry_new ();
567 d->newline_entry = gtk_entry_new ();
568 d->quote_entry = gtk_entry_new ();
569 d->header_check = gtk_check_button_new_with_mnemonic (
570 _("Prepend a _header"));
571
572 /* Advanced CSV options */
573 gtk_entry_set_text (GTK_ENTRY (d->delimiter_entry), ", ");
574 gtk_entry_set_text (GTK_ENTRY (d->quote_entry), "\"");
575 gtk_entry_set_text (GTK_ENTRY (d->newline_entry), "\\n");
576
577 gtk_table_set_row_spacings (GTK_TABLE (table), 5);
578 gtk_table_set_col_spacings (GTK_TABLE (table), 5);
579 label = gtk_label_new_with_mnemonic (_("_Value delimiter:"));
580 gtk_misc_set_alignment (GTK_MISC (label), 0, 0.0);
581 gtk_label_set_mnemonic_widget (GTK_LABEL (label), d->delimiter_entry);
582 gtk_table_attach (
583 GTK_TABLE (table), label, 0, 1, 0, 1,
584 (GtkAttachOptions) (GTK_FILL),
585 (GtkAttachOptions) (0), 0, 0);
586 gtk_table_attach (
587 GTK_TABLE (table), d->delimiter_entry, 1, 2, 0, 1,
588 (GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
589 (GtkAttachOptions) (0), 0, 0);
590 label = gtk_label_new_with_mnemonic (_("_Record delimiter:"));
591 gtk_misc_set_alignment (GTK_MISC (label), 0, 0.0);
592 gtk_label_set_mnemonic_widget (GTK_LABEL (label), d->newline_entry);
593 gtk_table_attach (
594 GTK_TABLE (table), label, 0, 1, 1, 2,
595 (GtkAttachOptions) (GTK_FILL),
596 (GtkAttachOptions) (0), 0, 0);
597 gtk_table_attach (
598 GTK_TABLE (table), d->newline_entry, 1, 2, 1, 2,
599 (GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
600 (GtkAttachOptions) (0), 0, 0);
601 label = gtk_label_new_with_mnemonic (_("_Encapsulate values with:"));
602 gtk_misc_set_alignment (GTK_MISC (label), 0, 0.0);
603 gtk_label_set_mnemonic_widget (GTK_LABEL (label), d->quote_entry);
604 gtk_table_attach (
605 GTK_TABLE (table), label, 0, 1, 2, 3,
606 (GtkAttachOptions) (GTK_FILL),
607 (GtkAttachOptions) (0), 0, 0);
608 gtk_table_attach (
609 GTK_TABLE (table), d->quote_entry, 1, 2, 2, 3,
610 (GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
611 (GtkAttachOptions) (0), 0, 0);
612
613 gtk_box_pack_start (GTK_BOX (vbox), d->header_check, TRUE, TRUE, 0);
614 gtk_box_pack_start (GTK_BOX (vbox), table, TRUE, TRUE, 0);
615 gtk_widget_show_all (vbox);
616
617 gtk_container_add (GTK_CONTAINER (csv_options), vbox);
618
619 return csv_options;
620 }
621
622 FormatHandler *csv_format_handler_new (void)
623 {
624 FormatHandler *handler = g_new (FormatHandler, 1);
625
626 handler->isdefault = FALSE;
627 handler->combo_label = _("Comma separated values (.csv)");
628 handler->filename_ext = ".csv";
629 handler->data = g_new (CsvPluginData, 1);
630 handler->options_widget = create_options_widget (handler);
631 handler->save = do_save_calendar_csv;
632
633 return handler;
634 }