1 /*
2    Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007 Bastien Nocera
3    Copyright (C) 2003, 2004 Colin Walters <walters@rhythmbox.org>
4 
5    The Gnome Library is free software; you can redistribute it and/or
6    modify it under the terms of the GNU Library General Public License as
7    published by the Free Software Foundation; either version 2 of the
8    License, or (at your option) any later version.
9 
10    The Gnome Library is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13    Library General Public License for more details.
14 
15    You should have received a copy of the GNU Library General Public
16    License along with the Gnome Library; see the file COPYING.LIB.  If not,
17    write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18    Boston, MA 02110-1301  USA.
19 
20    Author: Bastien Nocera <hadess@hadess.net>
21  */
22 
23 #include "config.h"
24 
25 #ifndef TOTEM_PL_PARSER_MINI
26 #include <string.h>
27 #include <glib.h>
28 #include <glib/gi18n-lib.h>
29 #include <libxml/tree.h>
30 #include <libxml/parser.h>
31 
32 #include "totem-pl-parser.h"
33 #endif /* !TOTEM_PL_PARSER_MINI */
34 
35 #include "totem-pl-parser-mini.h"
36 #include "totem-pl-parser-xspf.h"
37 #include "totem-pl-parser-private.h"
38 
39 #ifndef TOTEM_PL_PARSER_MINI
40 
41 #define SAFE_FREE(x) { if (x != NULL) xmlFree (x); }
42 
43 static void
debug_noop(void * ctx,const char * msg,...)44 debug_noop (void *ctx, const char *msg, ...)
45 {
46 	return;
47 }
48 
49 static xmlDocPtr
totem_pl_parser_parse_xml_file(GFile * file)50 totem_pl_parser_parse_xml_file (GFile *file)
51 {
52 	xmlDocPtr doc;
53 	char *contents;
54 	gsize size;
55 
56 	if (g_file_load_contents (file, NULL, &contents, &size, NULL, NULL) == FALSE)
57 		return NULL;
58 
59 	/* Try to remove HTML style comments */
60 	{
61 		char *needle;
62 
63 		while ((needle = strstr (contents, "<!--")) != NULL) {
64 			while (strncmp (needle, "-->", 3) != 0) {
65 				*needle = ' ';
66 				needle++;
67 				if (*needle == '\0')
68 					break;
69 			}
70 		}
71 	}
72 
73 	xmlSetGenericErrorFunc (NULL, (xmlGenericErrorFunc) debug_noop);
74 	doc = xmlParseMemory (contents, size);
75 	if (doc == NULL)
76 		doc = xmlRecoverMemory (contents, size);
77 	g_free (contents);
78 
79 	return doc;
80 }
81 
82 static struct {
83 	const char *field;
84 	const char *element;
85 } fields[] = {
86 	{ TOTEM_PL_PARSER_FIELD_TITLE, "title" },
87 	{ TOTEM_PL_PARSER_FIELD_AUTHOR, "creator" },
88 	{ TOTEM_PL_PARSER_FIELD_IMAGE_URI, "image" },
89 	{ TOTEM_PL_PARSER_FIELD_ALBUM, "album" },
90 	{ TOTEM_PL_PARSER_FIELD_DURATION_MS, "duration" },
91 	{ TOTEM_PL_PARSER_FIELD_GENRE, NULL },
92 	{ TOTEM_PL_PARSER_FIELD_STARTTIME, NULL },
93 	{ TOTEM_PL_PARSER_FIELD_SUBTITLE_URI, NULL },
94 	{ TOTEM_PL_PARSER_FIELD_PLAYING, NULL },
95 	{ TOTEM_PL_PARSER_FIELD_CONTENT_TYPE, NULL }
96 };
97 
98 gboolean
totem_pl_parser_save_xspf(TotemPlParser * parser,TotemPlPlaylist * playlist,GFile * output,const char * title,GCancellable * cancellable,GError ** error)99 totem_pl_parser_save_xspf (TotemPlParser    *parser,
100                            TotemPlPlaylist  *playlist,
101                            GFile            *output,
102                            const char       *title,
103                            GCancellable     *cancellable,
104                            GError          **error)
105 {
106         TotemPlPlaylistIter iter;
107 	GFileOutputStream *stream;
108 	char *buf;
109 	gboolean valid, success;
110 
111 	stream = g_file_replace (output, NULL, FALSE, G_FILE_CREATE_NONE, cancellable, error);
112 	if (stream == NULL)
113 		return FALSE;
114 
115 	buf = g_strdup_printf ("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
116 				"<playlist version=\"1\" xmlns=\"http://xspf.org/ns/0/\">\n"
117 				" <trackList>\n");
118 	success = totem_pl_parser_write_string (G_OUTPUT_STREAM (stream), buf, cancellable, error);
119 	g_free (buf);
120 	if (success == FALSE)
121 		return FALSE;
122 
123         valid = totem_pl_playlist_iter_first (playlist, &iter);
124 
125         while (valid) {
126 		char *uri, *uri_escaped, *relative;
127 		guint i;
128 		gboolean wrote_ext;
129 
130                 totem_pl_playlist_get (playlist, &iter,
131                                        TOTEM_PL_PARSER_FIELD_URI, &uri,
132                                        NULL);
133 
134 
135                 if (!uri) {
136 			valid = totem_pl_playlist_iter_next (playlist, &iter);
137                         continue;
138 		}
139 
140 		/* Whether we already wrote the GNOME extensions section header
141 		 * for that particular track */
142 		wrote_ext = FALSE;
143 
144 		relative = totem_pl_parser_relative (output, uri);
145 		uri_escaped = g_markup_escape_text (relative ? relative : uri, -1);
146 		buf = g_strdup_printf ("  <track>\n"
147                                        "   <location>%s</location>\n", uri_escaped);
148 		success = totem_pl_parser_write_string (G_OUTPUT_STREAM (stream), buf, cancellable, error);
149 		g_free (uri);
150 		g_free (uri_escaped);
151 		g_free (relative);
152 		g_free (buf);
153 
154                 if (success == FALSE)
155 			return FALSE;
156 
157 		for (i = 0; i < G_N_ELEMENTS (fields); i++) {
158 			char *str, *escaped;
159 
160 			totem_pl_playlist_get (playlist, &iter,
161 					       fields[i].field, &str,
162 					       NULL);
163 			if (!str || *str == '\0') {
164 				g_free (str);
165 				continue;
166 			}
167 			escaped = g_markup_escape_text (str, -1);
168 			g_free (str);
169 			if (!escaped)
170 				continue;
171 			if (g_str_equal (fields[i].field, TOTEM_PL_PARSER_FIELD_GENRE)) {
172 				buf = g_strdup_printf ("   <extension application=\"http://www.rhythmbox.org\">\n"
173 						       "     <genre>%s</genre>\n"
174 						       "   </extension>\n",
175 						       escaped);
176 			} else if (g_str_equal (fields[i].field, TOTEM_PL_PARSER_FIELD_SUBTITLE_URI) ||
177 				   g_str_equal (fields[i].field, TOTEM_PL_PARSER_FIELD_PLAYING) ||
178 				   g_str_equal (fields[i].field, TOTEM_PL_PARSER_FIELD_CONTENT_TYPE) ||
179 				   g_str_equal (fields[i].field, TOTEM_PL_PARSER_FIELD_STARTTIME)) {
180 				if (!wrote_ext) {
181 					buf = g_strdup_printf ("   <extension application=\"http://www.gnome.org\">\n"
182 							       "     <%s>%s</%s>\n",
183 							       fields[i].field, escaped, fields[i].field);
184 					wrote_ext = TRUE;
185 				} else {
186 					buf = g_strdup_printf ("     <%s>%s</%s>\n",
187 							       fields[i].field, escaped, fields[i].field);
188 				}
189 			} else if (g_str_equal (fields[i].field, TOTEM_PL_PARSER_FIELD_GENRE) == FALSE) {
190 				buf = g_strdup_printf ("   <%s>%s</%s>\n",
191 						       fields[i].element,
192 						       escaped,
193 						       fields[i].element);
194 			}
195 
196 			success = totem_pl_parser_write_string (G_OUTPUT_STREAM (stream), buf, cancellable, error);
197 			g_free (buf);
198 			g_free (escaped);
199 
200 			if (success == FALSE)
201 				break;
202 		}
203 
204                 if (success == FALSE)
205 			return FALSE;
206 
207 		if (wrote_ext)
208 			success = totem_pl_parser_write_string (G_OUTPUT_STREAM (stream), "   </extension>\n", cancellable, error);
209 
210 		if (success == FALSE)
211 			return FALSE;
212 
213 		success = totem_pl_parser_write_string (G_OUTPUT_STREAM (stream), "  </track>\n", cancellable, error);
214 		if (success == FALSE)
215 			return FALSE;
216 
217                 valid = totem_pl_playlist_iter_next (playlist, &iter);
218 	}
219 
220 	buf = g_strdup_printf (" </trackList>\n"
221                                "</playlist>");
222 	success = totem_pl_parser_write_string (G_OUTPUT_STREAM (stream), buf, cancellable, error);
223 	g_free (buf);
224 
225 	g_object_unref (stream);
226 
227 	return success;
228 }
229 
230 static gboolean
parse_bool_str(const char * str)231 parse_bool_str (const char *str)
232 {
233 	if (str == NULL)
234 		return FALSE;
235 	if (g_ascii_strcasecmp (str, "true") == 0)
236 		return TRUE;
237 	if (g_ascii_strcasecmp (str, "false") == 0)
238 		return FALSE;
239 	return atoi (str);
240 }
241 
242 static gboolean
parse_xspf_track(TotemPlParser * parser,GFile * base_file,xmlDocPtr doc,xmlNodePtr parent)243 parse_xspf_track (TotemPlParser *parser, GFile *base_file, xmlDocPtr doc,
244 		xmlNodePtr parent)
245 {
246 	xmlNodePtr node;
247 	xmlChar *title, *uri, *image_uri, *artist, *album, *duration, *moreinfo;
248 	xmlChar *download_uri, *id, *genre, *filesize, *subtitle, *mime_type;
249 	xmlChar *playing, *starttime;
250 	GFile *resolved;
251 	char *resolved_uri;
252 	TotemPlParserResult retval = TOTEM_PL_PARSER_RESULT_ERROR;
253 
254 	title = NULL;
255 	uri = NULL;
256 	image_uri = NULL;
257 	artist = NULL;
258 	album = NULL;
259 	duration = NULL;
260 	moreinfo = NULL;
261 	download_uri = NULL;
262 	id = NULL;
263 	genre = NULL;
264 	filesize = NULL;
265 	subtitle = NULL;
266 	mime_type = NULL;
267 	playing = NULL;
268 	starttime = NULL;
269 
270 	for (node = parent->children; node != NULL; node = node->next)
271 	{
272 		if (node->name == NULL)
273 			continue;
274 
275 		if (g_ascii_strcasecmp ((char *)node->name, "location") == 0)
276 			uri = xmlNodeListGetString (doc, node->xmlChildrenNode, 1);
277 		else if (g_ascii_strcasecmp ((char *)node->name, "title") == 0)
278 			title = xmlNodeListGetString (doc, node->xmlChildrenNode, 1);
279 		else if (g_ascii_strcasecmp ((char *)node->name, "image") == 0)
280 			image_uri = xmlNodeListGetString (doc, node->xmlChildrenNode, 1);
281 		/* Last.fm uses creator for the artist */
282 		else if (g_ascii_strcasecmp ((char *)node->name, "creator") == 0)
283 			artist = xmlNodeListGetString (doc, node->xmlChildrenNode, 1);
284 		else if (g_ascii_strcasecmp ((char *)node->name, "duration") == 0)
285 			duration = xmlNodeListGetString (doc, node->xmlChildrenNode, 1);
286 		else if (g_ascii_strcasecmp ((char *)node->name, "link") == 0) {
287 			xmlChar *rel;
288 
289 			rel = xmlGetProp (node, (const xmlChar *) "rel");
290 			if (rel != NULL) {
291 				if (g_ascii_strcasecmp ((char *) rel, "http://www.last.fm/trackpage") == 0)
292 					moreinfo = xmlNodeListGetString (doc, node->xmlChildrenNode, 1);
293 				else if (g_ascii_strcasecmp ((char *) rel, "http://www.last.fm/freeTrackURL") == 0)
294 					download_uri = xmlNodeListGetString (doc, node->xmlChildrenNode, 1);
295 				xmlFree (rel);
296 			} else {
297 				/* If we don't have a rel="", then it's not a last.fm playlist */
298 				moreinfo = xmlNodeListGetString (doc, node->xmlChildrenNode, 1);
299 			}
300 		/* Parse the genre extension for Rhythmbox */
301 		} else if (g_ascii_strcasecmp ((char *)node->name, "extension") == 0) {
302 			xmlChar *app;
303 			app = xmlGetProp (node, (const xmlChar *) "application");
304 			if (app != NULL && g_ascii_strcasecmp ((char *) app, "http://www.rhythmbox.org") == 0) {
305 				xmlNodePtr child;
306 				for (child = node->xmlChildrenNode ; child; child = child->next) {
307 					if (child->name != NULL &&
308 					    g_ascii_strcasecmp ((char *)child->name, "genre") == 0) {
309 						genre = xmlNodeListGetString (doc, child->xmlChildrenNode, 0);
310 						break;
311 					}
312 				}
313 			} else if (app != NULL && g_ascii_strcasecmp ((char *) app, "http://www.gnome.org") == 0) {
314 				xmlNodePtr child;
315 				for (child = node->xmlChildrenNode ; child; child = child->next) {
316 					if (child->name == NULL)
317 						continue;
318 					if (g_ascii_strcasecmp ((char *)child->name, "playing") == 0) {
319 						xmlChar *str;
320 						str = xmlNodeListGetString (doc, child->xmlChildrenNode, 0);
321 						playing = parse_bool_str ((char *) str) ? (xmlChar *) "true" : NULL;
322 					} else if (g_ascii_strcasecmp ((char *)child->name, "subtitle") == 0) {
323 						subtitle = xmlNodeListGetString (doc, child->xmlChildrenNode, 0);
324 					} else if (g_ascii_strcasecmp ((char *)child->name, "mime-type") == 0) {
325 						mime_type = xmlNodeListGetString (doc, child->xmlChildrenNode, 0);
326 					} else if (g_ascii_strcasecmp ((char *)child->name, "starttime") == 0) {
327 						starttime = xmlNodeListGetString (doc, child->xmlChildrenNode, 0);
328 					}
329 				}
330 			} else if (app != NULL && g_ascii_strcasecmp ((char *) app, "http://www.last.fm") == 0) {
331 				xmlNodePtr child;
332 				for (child = node->xmlChildrenNode ; child; child = child->next) {
333 					if (child->name != NULL) {
334 						if (g_ascii_strcasecmp ((char *)child->name, "trackauth") == 0) {
335 							id = xmlNodeListGetString (doc, child->xmlChildrenNode, 0);
336 							continue;
337 						}
338 						if (g_ascii_strcasecmp ((char *)child->name, "freeTrackURL") == 0) {
339 							download_uri = xmlNodeListGetString (doc, child->xmlChildrenNode, 0);
340 							continue;
341 						}
342 					}
343 				}
344 			}
345 			g_clear_pointer (&app, xmlFree);
346 		/* Parse Amazon AMZ extensions */
347 		} else if (g_ascii_strcasecmp ((char *)node->name, "meta") == 0) {
348 			xmlChar *rel;
349 
350 			rel = xmlGetProp (node, (const xmlChar *) "rel");
351 			if (rel != NULL) {
352 				if (g_ascii_strcasecmp ((char *) rel, "http://www.amazon.com/dmusic/primaryGenre") == 0)
353 					genre = xmlNodeListGetString (doc, node->xmlChildrenNode, 1);
354 				else if (g_ascii_strcasecmp ((char *) rel, "http://www.amazon.com/dmusic/ASIN") == 0)
355 					id = xmlNodeListGetString (doc, node->xmlChildrenNode, 1);
356 				else if (g_ascii_strcasecmp ((char *) rel, "http://www.amazon.com/dmusic/fileSize") == 0)
357 					filesize = xmlNodeListGetString (doc, node->xmlChildrenNode, 1);
358 				xmlFree (rel);
359 			}
360 		} else if (g_ascii_strcasecmp ((char *)node->name, "album") == 0)
361 			album = xmlNodeListGetString (doc, node->xmlChildrenNode, 1);
362 		else if (g_ascii_strcasecmp ((char *)node->name, "trackauth") == 0)
363 			id = xmlNodeListGetString (doc, node->xmlChildrenNode, 1);
364 	}
365 
366 	if (uri == NULL) {
367 		retval = TOTEM_PL_PARSER_RESULT_ERROR;
368 		goto bail;
369 	}
370 
371 	resolved_uri = totem_pl_parser_resolve_uri (base_file, (char *) uri);
372 
373 	if (g_strcmp0 (resolved_uri, (char *) uri) == 0) {
374 		g_free (resolved_uri);
375 		totem_pl_parser_add_uri (parser,
376 					 TOTEM_PL_PARSER_FIELD_URI, uri,
377 					 TOTEM_PL_PARSER_FIELD_TITLE, title,
378 					 TOTEM_PL_PARSER_FIELD_DURATION_MS, duration,
379 					 TOTEM_PL_PARSER_FIELD_IMAGE_URI, image_uri,
380 					 TOTEM_PL_PARSER_FIELD_AUTHOR, artist,
381 					 TOTEM_PL_PARSER_FIELD_ALBUM, album,
382 					 TOTEM_PL_PARSER_FIELD_MOREINFO, moreinfo,
383 					 TOTEM_PL_PARSER_FIELD_DOWNLOAD_URI, download_uri,
384 					 TOTEM_PL_PARSER_FIELD_ID, id,
385 					 TOTEM_PL_PARSER_FIELD_GENRE, genre,
386 					 TOTEM_PL_PARSER_FIELD_FILESIZE, filesize,
387 					 TOTEM_PL_PARSER_FIELD_SUBTITLE_URI, subtitle,
388 					 TOTEM_PL_PARSER_FIELD_PLAYING, playing,
389 					 TOTEM_PL_PARSER_FIELD_CONTENT_TYPE, mime_type,
390 					 TOTEM_PL_PARSER_FIELD_STARTTIME, starttime,
391 					 NULL);
392 	} else {
393 		resolved = g_file_new_for_uri (resolved_uri);
394 		g_free (resolved_uri);
395 
396 		totem_pl_parser_add_uri (parser,
397 					 TOTEM_PL_PARSER_FIELD_FILE, resolved,
398 					 TOTEM_PL_PARSER_FIELD_TITLE, title,
399 					 TOTEM_PL_PARSER_FIELD_DURATION_MS, duration,
400 					 TOTEM_PL_PARSER_FIELD_IMAGE_URI, image_uri,
401 					 TOTEM_PL_PARSER_FIELD_AUTHOR, artist,
402 					 TOTEM_PL_PARSER_FIELD_ALBUM, album,
403 					 TOTEM_PL_PARSER_FIELD_MOREINFO, moreinfo,
404 					 TOTEM_PL_PARSER_FIELD_DOWNLOAD_URI, download_uri,
405 					 TOTEM_PL_PARSER_FIELD_ID, id,
406 					 TOTEM_PL_PARSER_FIELD_GENRE, genre,
407 					 TOTEM_PL_PARSER_FIELD_FILESIZE, filesize,
408 					 TOTEM_PL_PARSER_FIELD_SUBTITLE_URI, subtitle,
409 					 TOTEM_PL_PARSER_FIELD_PLAYING, playing,
410 					 TOTEM_PL_PARSER_FIELD_CONTENT_TYPE, mime_type,
411 					 TOTEM_PL_PARSER_FIELD_STARTTIME, starttime,
412 					 NULL);
413 		g_object_unref (resolved);
414 	}
415 
416 	retval = TOTEM_PL_PARSER_RESULT_SUCCESS;
417 
418 bail:
419 	SAFE_FREE (title);
420 	SAFE_FREE (uri);
421 	SAFE_FREE (image_uri);
422 	SAFE_FREE (artist);
423 	SAFE_FREE (album);
424 	SAFE_FREE (duration);
425 	SAFE_FREE (moreinfo);
426 	SAFE_FREE (download_uri);
427 	SAFE_FREE (id);
428 	SAFE_FREE (genre);
429 
430 	return retval;
431 }
432 
433 static gboolean
parse_xspf_trackList(TotemPlParser * parser,GFile * base_file,xmlDocPtr doc,xmlNodePtr parent)434 parse_xspf_trackList (TotemPlParser *parser, GFile *base_file, xmlDocPtr doc,
435 		xmlNodePtr parent)
436 {
437 	xmlNodePtr node;
438 	TotemPlParserResult retval = TOTEM_PL_PARSER_RESULT_ERROR;
439 
440 	for (node = parent->children; node != NULL; node = node->next)
441 	{
442 		if (node->name == NULL)
443 			continue;
444 
445 		if (g_ascii_strcasecmp ((char *)node->name, "track") == 0)
446 			if (parse_xspf_track (parser, base_file, doc, node) != FALSE)
447 				retval = TOTEM_PL_PARSER_RESULT_SUCCESS;
448 	}
449 
450 	return retval;
451 }
452 
453 static gboolean
parse_xspf_entries(TotemPlParser * parser,GFile * file,GFile * base_file,xmlDocPtr doc,xmlNodePtr parent)454 parse_xspf_entries (TotemPlParser *parser,
455 		    GFile         *file,
456 		    GFile         *base_file,
457 		    xmlDocPtr      doc,
458 		    xmlNodePtr     parent)
459 {
460 	xmlNodePtr node;
461 	TotemPlParserResult retval = TOTEM_PL_PARSER_RESULT_ERROR;
462 	const xmlChar *title;
463 	char *uri;
464 
465 	uri = g_file_get_uri (file);
466 	title = NULL;
467 
468 	/* We go through the list twice to avoid the playlist-started
469 	 * signal being out of order */
470 
471 	for (node = parent->children; node != NULL; node = node->next) {
472 		if (node->name == NULL)
473 			continue;
474 
475 		if (g_ascii_strcasecmp ((char *)node->name, "title") == 0) {
476 			title = node->name;
477 			break;
478 		}
479 	}
480 
481 	totem_pl_parser_add_uri (parser,
482 				 TOTEM_PL_PARSER_FIELD_IS_PLAYLIST, TRUE,
483 				 TOTEM_PL_PARSER_FIELD_URI, uri,
484 				 TOTEM_PL_PARSER_FIELD_TITLE, title,
485 				 TOTEM_PL_PARSER_FIELD_CONTENT_TYPE, "application/xspf+xml",
486 				 NULL);
487 
488 	for (node = parent->children; node != NULL; node = node->next) {
489 		if (node->name == NULL)
490 			continue;
491 
492 		if (g_ascii_strcasecmp ((char *)node->name, "trackList") == 0) {
493 			if (parse_xspf_trackList (parser, base_file, doc, node) != FALSE)
494 				retval = TOTEM_PL_PARSER_RESULT_SUCCESS;
495 		}
496 	}
497 
498 	if (uri != NULL) {
499 		totem_pl_parser_playlist_end (parser, uri);
500 		g_free (uri);
501 	}
502 
503 	return retval;
504 }
505 
506 static gboolean
is_xspf_doc(xmlDocPtr doc)507 is_xspf_doc (xmlDocPtr doc)
508 {
509 	/* If the document has no root, or no name */
510 	if(!doc ||
511 	   !doc->children ||
512 	   !doc->children->name ||
513 	   g_ascii_strcasecmp ((char *)doc->children->name, "playlist") != 0) {
514 		return FALSE;
515 	}
516 	return TRUE;
517 }
518 
519 TotemPlParserResult
totem_pl_parser_add_xspf_with_contents(TotemPlParser * parser,GFile * file,GFile * base_file,const char * contents,TotemPlParseData * parse_data)520 totem_pl_parser_add_xspf_with_contents (TotemPlParser *parser,
521 					GFile *file,
522 					GFile *base_file,
523 					const char *contents,
524 					TotemPlParseData *parse_data)
525 {
526 	xmlDocPtr doc;
527 	xmlNodePtr node;
528 	TotemPlParserResult retval = TOTEM_PL_PARSER_RESULT_UNHANDLED;
529 
530 	xmlSetGenericErrorFunc (NULL, (xmlGenericErrorFunc) debug_noop);
531 	doc = xmlParseMemory (contents, strlen (contents));
532 	if (doc == NULL)
533 		doc = xmlRecoverMemory (contents, strlen (contents));
534 
535 	if (is_xspf_doc (doc) == FALSE) {
536 		if (doc != NULL)
537 			xmlFreeDoc(doc);
538 		return TOTEM_PL_PARSER_RESULT_ERROR;
539 	}
540 
541 	for (node = doc->children; node != NULL; node = node->next) {
542 		if (parse_xspf_entries (parser, file, base_file, doc, node) != FALSE)
543 			retval = TOTEM_PL_PARSER_RESULT_SUCCESS;
544 	}
545 
546 	xmlFreeDoc(doc);
547 	return retval;
548 }
549 
550 TotemPlParserResult
totem_pl_parser_add_xspf(TotemPlParser * parser,GFile * file,GFile * base_file,TotemPlParseData * parse_data,gpointer data)551 totem_pl_parser_add_xspf (TotemPlParser *parser,
552 			  GFile *file,
553 			  GFile *base_file,
554 			  TotemPlParseData *parse_data,
555 			  gpointer data)
556 {
557 	xmlDocPtr doc;
558 	xmlNodePtr node;
559 	TotemPlParserResult retval = TOTEM_PL_PARSER_RESULT_UNHANDLED;
560 
561 	doc = totem_pl_parser_parse_xml_file (file);
562 	if (is_xspf_doc (doc) == FALSE) {
563 		if (doc != NULL)
564 			xmlFreeDoc(doc);
565 		return TOTEM_PL_PARSER_RESULT_ERROR;
566 	}
567 
568 	for (node = doc->children; node != NULL; node = node->next) {
569 		if (parse_xspf_entries (parser, file, base_file, doc, node) != FALSE)
570 			retval = TOTEM_PL_PARSER_RESULT_SUCCESS;
571 	}
572 
573 	xmlFreeDoc(doc);
574 	return retval;
575 }
576 #endif /* !TOTEM_PL_PARSER_MINI */
577 
578