evolution-3.6.4/plugins/save-calendar/csv-format.c

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 }