1 /*
2  * This program is free software; you can redistribute it and/or modify it
3  * under the terms of the GNU Lesser General Public License as published by
4  * the Free Software Foundation.
5  *
6  * This program is distributed in the hope that it will be useful, but
7  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
8  * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
9  * for more details.
10  *
11  * You should have received a copy of the GNU Lesser General Public License
12  * along with this program; if not, see <http://www.gnu.org/licenses/>.
13  *
14  *
15  * Authors:
16  *		Philip Van Hoof <pvanhoof@gnome.org>
17  *
18  * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
19  *
20  */
21 
22 #include "evolution-config.h"
23 
24 #include <string.h>
25 #include <glib/gi18n.h>
26 
27 #include <libxml/xmlmemory.h>
28 #include <libxml/parser.h>
29 #include <libxml/tree.h>
30 #include <libxml/xmlIO.h>
31 #include <libxml/xpath.h>
32 
33 #include "format-handler.h"
34 
35 static void	add_string_to_rdf		(xmlNodePtr node,
36 						 const gchar *tag,
37 						 const gchar *value);
38 
39 /* Use { */
40 
41 /* #include <calendar/gui/calendar-config-keys.h> */
42 /* #include <calendar/gui/calendar-config.h> */
43 
44 /* } or { */
45 #define CALENDAR_CONFIG_PREFIX "/apps/evolution/calendar"
46 #define CALENDAR_CONFIG_TIMEZONE CALENDAR_CONFIG_PREFIX "/display/timezone"
47 
48 static gchar *
calendar_config_get_timezone(void)49 calendar_config_get_timezone (void)
50 {
51 	GSettings *settings;
52 	gchar *retval = NULL;
53 
54 	settings = e_util_ref_settings ("org.gnome.evolution.calendar");
55 	retval = g_settings_get_string (settings, "timezone");
56 	g_object_unref (settings);
57 	if (!retval)
58 		retval = g_strdup ("UTC");
59 
60 	return retval;
61 }
62 /* } */
63 
64 enum { /* XML helper enum */
65 	ECALCOMPONENTTEXT,
66 	ECALCOMPONENTATTENDEE,
67 	CONSTCHAR
68 };
69 
70 static void
display_error_message(GtkWidget * parent,const gchar * error_message)71 display_error_message (GtkWidget *parent,
72                        const gchar *error_message)
73 {
74 	GtkWidget *dialog;
75 
76 	dialog = gtk_message_dialog_new (
77 		GTK_WINDOW (parent), 0, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
78 		"%s", error_message);
79 	gtk_dialog_run (GTK_DIALOG (dialog));
80 	gtk_widget_destroy (dialog);
81 }
82 
83 /* Some helpers for the xml stuff */
84 static void
add_list_to_rdf(xmlNodePtr node,const gchar * tag,GSList * list_in,gint type)85 add_list_to_rdf (xmlNodePtr node,
86                  const gchar *tag,
87                  GSList *list_in,
88                  gint type)
89 {
90 	if (list_in) {
91 		GSList *list = list_in;
92 
93 		while (list) {
94 			const gchar *str = NULL;
95 
96 			switch (type) {
97 			case ECALCOMPONENTATTENDEE:
98 				str = itip_strip_mailto (e_cal_component_attendee_get_value ((ECalComponentAttendee *) list->data));
99 				break;
100 			case ECALCOMPONENTTEXT:
101 				str = e_cal_component_text_get_value ((ECalComponentText *) list->data);
102 				break;
103 			case CONSTCHAR:
104 			default:
105 				str = list->data;
106 				break;
107 			}
108 
109 			add_string_to_rdf (node, tag, str);
110 
111 			list = g_slist_next (list);
112 		}
113 	}
114 }
115 
116 static void
add_nummeric_to_rdf(xmlNodePtr node,const gchar * tag,gint nummeric)117 add_nummeric_to_rdf (xmlNodePtr node,
118                      const gchar *tag,
119                      gint nummeric)
120 {
121 	if (nummeric >= 0) {
122 		gchar *value = g_strdup_printf ("%d", nummeric);
123 		xmlNodePtr cur_node = xmlNewChild (node, NULL, (guchar *) tag, (guchar *) value);
124 		xmlSetProp (cur_node, (const guchar *)"rdf:datatype", (const guchar *)"http://www.w3.org/2001/XMLSchema#integer");
125 		g_free (value);
126 	}
127 }
128 
129 static void
add_time_to_rdf(xmlNodePtr node,const gchar * tag,ICalTime * time)130 add_time_to_rdf (xmlNodePtr node,
131                  const gchar *tag,
132                  ICalTime *time)
133 {
134 	if (time) {
135 		xmlNodePtr cur_node = NULL;
136 		struct tm mytm = e_cal_util_icaltime_to_tm (time);
137 		gchar *str = (gchar *) g_malloc (sizeof (gchar) * 200);
138 		gchar *tmp = NULL;
139 		gchar *timezone;
140 		/*
141 		 * Translator: the %FT%T is the thirth argument for a strftime function.
142 		 * It lets you define the formatting of the date in the rdf-file.
143 		 * Also check out http://www.w3.org/2002/12/cal/tzd
144 		 * */
145 		e_utf8_strftime (str, 200, _("%FT%T"), &mytm);
146 
147 		cur_node = xmlNewChild (node, NULL, (guchar *) tag, (guchar *) str);
148 
149 		/* Not sure about this property */
150 		timezone = calendar_config_get_timezone ();
151 		tmp = g_strdup_printf ("http://www.w3.org/2002/12/cal/tzd/%s#tz", timezone);
152 		xmlSetProp (cur_node, (const guchar *)"rdf:datatype", (guchar *) tmp);
153 		g_free (tmp);
154 		g_free (timezone);
155 		g_free (str);
156 	}
157 }
158 
159 static void
add_string_to_rdf(xmlNodePtr node,const gchar * tag,const gchar * value)160 add_string_to_rdf (xmlNodePtr node,
161                    const gchar *tag,
162                    const gchar *value)
163 {
164 	if (value) {
165 		xmlNodePtr cur_node = NULL;
166 		cur_node = xmlNewChild (node, NULL, (guchar *) tag, (guchar *) value);
167 		xmlSetProp (cur_node, (const guchar *)"rdf:datatype", (const guchar *)"http://www.w3.org/2001/XMLSchema#string");
168 	}
169 }
170 
171 static void
do_save_calendar_rdf(FormatHandler * handler,ESourceSelector * selector,EClientCache * client_cache,gchar * dest_uri)172 do_save_calendar_rdf (FormatHandler *handler,
173                       ESourceSelector *selector,
174 		      EClientCache *client_cache,
175                       gchar *dest_uri)
176 {
177 
178 	/*
179 	 * According to some documentation about CSV, newlines 'are' allowed
180 	 * in CSV-files. But you 'do' have to put the value between quotes.
181 	 * The helper 'string_needsquotes' will check for that
182 	 *
183 	 * http://www.creativyst.com/Doc/Articles/CSV/CSV01.htm
184 	 * http://www.creativyst.com/cgi-bin/Prod/15/eg/csv2xml.pl
185 	 */
186 
187 	ESource *primary_source;
188 	EClient *source_client;
189 	GError *error = NULL;
190 	GSList *objects = NULL;
191 	gchar *temp = NULL;
192 	GOutputStream *stream;
193 
194 	if (!dest_uri)
195 		return;
196 
197 	/* open source client */
198 	primary_source = e_source_selector_ref_primary_selection (selector);
199 	source_client = e_client_cache_get_client_sync (client_cache,
200 		primary_source, e_source_selector_get_extension_name (selector), 30, NULL, &error);
201 	g_object_unref (primary_source);
202 
203 	/* Sanity check. */
204 	g_return_if_fail (
205 		((source_client != NULL) && (error == NULL)) ||
206 		((source_client == NULL) && (error != NULL)));
207 
208 	if (source_client == NULL) {
209 		display_error_message (
210 			gtk_widget_get_toplevel (GTK_WIDGET (selector)),
211 			error->message);
212 		g_error_free (error);
213 		return;
214 	}
215 
216 	stream = open_for_writing (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (selector))), dest_uri, &error);
217 
218 	if (stream && e_cal_client_get_object_list_as_comps_sync (E_CAL_CLIENT (source_client), "#t", &objects, NULL, NULL)) {
219 		GSList *iter;
220 
221 		xmlBufferPtr buffer = xmlBufferCreate ();
222 		xmlDocPtr doc = xmlNewDoc ((xmlChar *) "1.0");
223 		xmlNodePtr fnode;
224 
225 		doc->children = xmlNewDocNode (doc, NULL, (const guchar *)"rdf:RDF", NULL);
226 		xmlSetProp (doc->children, (const guchar *)"xmlns:rdf", (const guchar *)"http://www.w3.org/1999/02/22-rdf-syntax-ns#");
227 		xmlSetProp (doc->children, (const guchar *)"xmlns", (const guchar *)"http://www.w3.org/2002/12/cal/ical#");
228 
229 		fnode = xmlNewChild (doc->children, NULL, (const guchar *)"Vcalendar", NULL);
230 
231 		/* Should Evolution publicise these? */
232 		xmlSetProp (fnode, (const guchar *)"xmlns:x-wr", (const guchar *)"http://www.w3.org/2002/12/cal/prod/Apple_Comp_628d9d8459c556fa#");
233 		xmlSetProp (fnode, (const guchar *)"xmlns:x-lic", (const guchar *)"http://www.w3.org/2002/12/cal/prod/Apple_Comp_628d9d8459c556fa#");
234 
235 		/* Not sure if it's correct like this */
236 		xmlNewChild (fnode, NULL, (const guchar *)"prodid", (const guchar *)"-//" PACKAGE " " VERSION VERSION_SUBSTRING " " VERSION_COMMENT "//iCal 1.0//EN");
237 
238 		/* Assuming GREGORIAN is the only supported calendar scale */
239 		xmlNewChild (fnode, NULL, (const guchar *)"calscale", (const guchar *)"GREGORIAN");
240 
241 		temp = calendar_config_get_timezone ();
242 		xmlNewChild (fnode, NULL, (const guchar *)"x-wr:timezone", (guchar *) temp);
243 		g_free (temp);
244 
245 		xmlNewChild (fnode, NULL, (const guchar *)"method", (const guchar *)"PUBLISH");
246 
247 		xmlNewChild (fnode, NULL, (const guchar *)"x-wr:relcalid", (guchar *) e_source_get_uid (primary_source));
248 
249 		xmlNewChild (fnode, NULL, (const guchar *)"x-wr:calname", (guchar *) e_source_get_display_name (primary_source));
250 
251 		/* Version of this RDF-format */
252 		xmlNewChild (fnode, NULL, (const guchar *)"version", (const guchar *)"2.0");
253 
254 		for (iter = objects; iter; iter = iter->next) {
255 			ECalComponent *comp = iter->data;
256 			const gchar *temp_constchar;
257 			gchar *tmp_str;
258 			GSList *temp_list;
259 			ECalComponentDateTime *temp_dt;
260 			ICalTime *temp_time;
261 			gint temp_int;
262 			ECalComponentText *temp_comptext;
263 			xmlNodePtr c_node = xmlNewChild (fnode, NULL, (const guchar *)"component", NULL);
264 			xmlNodePtr node = xmlNewChild (c_node, NULL, (const guchar *)"Vevent", NULL);
265 
266 			/* Getting the stuff */
267 			temp_constchar = e_cal_component_get_uid (comp);
268 			tmp_str = g_strdup_printf ("#%s", temp_constchar);
269 			xmlSetProp (node, (const guchar *)"about", (guchar *) tmp_str);
270 			g_free (tmp_str);
271 			add_string_to_rdf (node, "uid", temp_constchar);
272 
273 			temp_comptext = e_cal_component_get_summary (comp);
274 			if (temp_comptext)
275 				add_string_to_rdf (node, "summary", e_cal_component_text_get_value (temp_comptext));
276 			e_cal_component_text_free (temp_comptext);
277 
278 			temp_list = e_cal_component_get_descriptions (comp);
279 			add_list_to_rdf (node, "description", temp_list, ECALCOMPONENTTEXT);
280 			g_slist_free_full (temp_list, e_cal_component_text_free);
281 
282 			temp_list = e_cal_component_get_categories_list (comp);
283 			add_list_to_rdf (node, "categories", temp_list, CONSTCHAR);
284 			g_slist_free_full (temp_list, g_free);
285 
286 			temp_list = e_cal_component_get_comments (comp);
287 			add_list_to_rdf (node, "comment", temp_list, ECALCOMPONENTTEXT);
288 			g_slist_free_full (temp_list, e_cal_component_text_free);
289 
290 			temp_time = e_cal_component_get_completed (comp);
291 			add_time_to_rdf (node, "completed", temp_time);
292 			g_clear_object (&temp_time);
293 
294 			temp_time = e_cal_component_get_created (comp);
295 			add_time_to_rdf (node, "created", temp_time);
296 			g_clear_object (&temp_time);
297 
298 			temp_list = e_cal_component_get_contacts (comp);
299 			add_list_to_rdf (node, "contact", temp_list, ECALCOMPONENTTEXT);
300 			g_slist_free_full (temp_list, e_cal_component_text_free);
301 
302 			temp_dt = e_cal_component_get_dtstart (comp);
303 			add_time_to_rdf (node, "dtstart", temp_dt && e_cal_component_datetime_get_value (temp_dt) ?
304 				e_cal_component_datetime_get_value (temp_dt) : NULL);
305 			e_cal_component_datetime_free (temp_dt);
306 
307 			temp_dt = e_cal_component_get_dtend (comp);
308 			add_time_to_rdf (node, "dtend", temp_dt && e_cal_component_datetime_get_value (temp_dt) ?
309 				e_cal_component_datetime_get_value (temp_dt) : NULL);
310 			e_cal_component_datetime_free (temp_dt);
311 
312 			temp_dt = e_cal_component_get_due (comp);
313 			add_time_to_rdf (node, "due", temp_dt && e_cal_component_datetime_get_value (temp_dt) ?
314 				e_cal_component_datetime_get_value (temp_dt) : NULL);
315 			e_cal_component_datetime_free (temp_dt);
316 
317 			temp_int = e_cal_component_get_percent_complete (comp);
318 			add_nummeric_to_rdf (node, "percentComplete", temp_int);
319 
320 			temp_int = e_cal_component_get_priority (comp);
321 			add_nummeric_to_rdf (node, "priority", temp_int);
322 
323 			tmp_str = e_cal_component_get_url (comp);
324 			add_string_to_rdf (node, "URL", tmp_str);
325 			g_free (tmp_str);
326 
327 			if (e_cal_component_has_attendees (comp)) {
328 				temp_list = e_cal_component_get_attendees (comp);
329 				add_list_to_rdf (node, "attendee", temp_list, ECALCOMPONENTATTENDEE);
330 				g_slist_free_full (temp_list, e_cal_component_attendee_free);
331 			}
332 
333 			tmp_str = e_cal_component_get_location (comp);
334 			add_string_to_rdf (node, "location", tmp_str);
335 			g_free (tmp_str);
336 
337 			temp_time = e_cal_component_get_last_modified (comp);
338 			add_time_to_rdf (node, "lastModified",temp_time);
339 			g_clear_object (&temp_time);
340 		}
341 
342 		/* I used a buffer rather than xmlDocDump: I want gio support */
343 		xmlNodeDump (buffer, doc, doc->children, 2, 1);
344 
345 		g_output_stream_write_all (stream, xmlBufferContent (buffer), xmlBufferLength (buffer), NULL, NULL, &error);
346 		g_output_stream_close (stream, NULL, NULL);
347 
348 		e_util_free_nullable_object_slist (objects);
349 
350 		xmlBufferFree (buffer);
351 		xmlFreeDoc (doc);
352 	}
353 
354 	if (stream)
355 		g_object_unref (stream);
356 
357 	g_object_unref (source_client);
358 
359 	if (error != NULL) {
360 		display_error_message (
361 			gtk_widget_get_toplevel (GTK_WIDGET (selector)),
362 			error->message);
363 		g_error_free (error);
364 	}
365 }
366 
367 FormatHandler *
rdf_format_handler_new(void)368 rdf_format_handler_new (void)
369 {
370 	FormatHandler *handler = g_new0 (FormatHandler, 1);
371 
372 	handler->isdefault = FALSE;
373 	handler->combo_label = _("RDF (.rdf)");
374 	handler->filename_ext = ".rdf";
375 	handler->options_widget = NULL;
376 	handler->save = do_save_calendar_rdf;
377 
378 	return handler;
379 }
380