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