xspf.c 31.6 KB
Newer Older
Clément Stenac's avatar
Clément Stenac committed
1
/*******************************************************************************
2 3 4 5 6 7 8
 * xspf.c : XSPF playlist import functions
 *******************************************************************************
 * Copyright (C) 2006 the VideoLAN team
 * $Id$
 *
 * Authors: Daniel Stränger <vlc at schmaller dot de>
 *          Yoann Peronneau <yoann@videolan.org>
9 10 11 12 13 14 15 16 17 18 19 20 21 22
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
Rafaël Carré's avatar
Rafaël Carré committed
23
 ******************************************************************************/
24 25 26 27 28
/**
 * \file modules/demux/playlist/xspf.c
 * \brief XSPF playlist import functions
 */

29 30 31 32
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

33
#include <vlc_common.h>
Clément Stenac's avatar
Clément Stenac committed
34
#include <vlc_demux.h>
35

36 37 38
#include <vlc_xml.h>
#include <vlc_strings.h>
#include <vlc_url.h>
39
#include "xspf.h"
40
#include "playlist.h"
41

42
struct demux_sys_t
Clément Stenac's avatar
Clément Stenac committed
43
{
44 45
    input_item_t **pp_tracklist;
    int i_tracklist_entries;
46
    int i_track_id;
Christophe Mutricy's avatar
Christophe Mutricy committed
47
    char * psz_base;
Clément Stenac's avatar
Clément Stenac committed
48 49
};

50 51 52
static int Control( demux_t *, int, va_list );
static int Demux( demux_t * );

53 54 55
/**
 * \brief XSPF submodule initialization function
 */
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
56
int Import_xspf( vlc_object_t *p_this )
57
{
58
    DEMUX_BY_EXTENSION_OR_FORCED_MSG( ".xspf", "xspf-open",
59
                                      "using XSPF playlist reader" );
60 61 62
    return VLC_SUCCESS;
}

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
63
void Close_xspf( vlc_object_t *p_this )
64 65
{
    demux_t *p_demux = (demux_t *)p_this;
66 67 68 69 70 71
    int i;
    for(i = 0; i < p_demux->p_sys->i_tracklist_entries; i++)
    {
        if(p_demux->p_sys->pp_tracklist[i])
            vlc_gc_decref( p_demux->p_sys->pp_tracklist[i] );
    }
72 73
    free( p_demux->p_sys->pp_tracklist );
    free( p_demux->p_sys->psz_base );
74 75 76
    free( p_demux->p_sys );
}

77 78 79
/**
 * \brief demuxer function for XSPF parsing
 */
80
int Demux( demux_t *p_demux )
81
{
82
    int i_ret = 1;
83 84 85
    xml_t *p_xml = NULL;
    xml_reader_t *p_xml_reader = NULL;
    char *psz_name = NULL;
Clément Stenac's avatar
Clément Stenac committed
86
    INIT_PLAYLIST_STUFF;
87 88
    p_demux->p_sys->pp_tracklist = NULL;
    p_demux->p_sys->i_tracklist_entries = 0;
89
    p_demux->p_sys->i_track_id = -1;
Christophe Mutricy's avatar
Christophe Mutricy committed
90
    p_demux->p_sys->psz_base = NULL;
91 92 93 94

    /* create new xml parser from stream */
    p_xml = xml_Create( p_demux );
    if( !p_xml )
95
        i_ret = -1;
96 97 98 99
    else
    {
        p_xml_reader = xml_ReaderCreate( p_xml, p_demux->s );
        if( !p_xml_reader )
100
            i_ret = -1;
101 102
    }

103
    /* locating the root node */
104
    if( i_ret == 1 )
105 106
    {
        do
107
        {
108 109 110
            if( xml_ReaderRead( p_xml_reader ) != 1 )
            {
                msg_Err( p_demux, "can't read xml stream" );
111
                i_ret = -1;
112 113 114 115
            }
        } while( i_ret == VLC_SUCCESS &&
                 xml_ReaderNodeType( p_xml_reader ) != XML_READER_STARTELEM );
    }
116
    /* checking root node name */
117
    if( i_ret == 1 )
118 119
    {
        psz_name = xml_ReaderName( p_xml_reader );
120
        if( !psz_name || strcmp( psz_name, "playlist" ) )
121
        {
122
            msg_Err( p_demux, "invalid root node name: %s", psz_name );
123
            i_ret = -1;
124
        }
125
        FREE_NAME();
126 127
    }

128
    if( i_ret == 1 )
129
        i_ret = parse_playlist_node( p_demux, p_current_input,
130 131
                                     p_xml_reader, "playlist" ) ? 0 : -1;

Rafaël Carré's avatar
Rafaël Carré committed
132 133 134 135 136
    int i;
    for( i = 0 ; i < p_demux->p_sys->i_tracklist_entries ; i++ )
    {
        input_item_t *p_new_input = p_demux->p_sys->pp_tracklist[i];
        if( p_new_input )
Pierre d'Herbemont's avatar
Pierre d'Herbemont committed
137
        {
138
            input_item_AddSubItem( p_current_input, p_new_input );
Pierre d'Herbemont's avatar
Pierre d'Herbemont committed
139
        }
Rafaël Carré's avatar
Rafaël Carré committed
140
    }
141

Clément Stenac's avatar
Clément Stenac committed
142
    HANDLE_PLAY_AND_RELEASE;
143
    if( p_xml_reader )
144
        xml_ReaderDelete( p_xml, p_xml_reader );
145
    if( p_xml )
146
        xml_Delete( p_xml );
147
    return i_ret; /* Needed for correct operation of go back */
148 149 150
}

/** \brief dummy function for demux callback interface */
151
static int Control( demux_t *p_demux, int i_query, va_list args )
152
{
153
    VLC_UNUSED(p_demux); VLC_UNUSED(i_query); VLC_UNUSED(args);
154 155
    return VLC_EGENERIC;
}
Clément Stenac's avatar
Clément Stenac committed
156

157 158 159
/**
 * \brief parse the root node of a XSPF playlist
 * \param p_demux demuxer instance
160
 * \param p_input_item current input item
161 162 163
 * \param p_xml_reader xml reader instance
 * \param psz_element name of element to parse
 */
164
static bool parse_playlist_node COMPLEX_INTERFACE
165
{
166 167
    char *psz_name = NULL;
    char *psz_value = NULL;
168
    bool b_version_found = false;
169
    int i_node;
170
    xml_elem_hnd_t *p_handler = NULL;
171 172 173 174

    xml_elem_hnd_t pl_elements[] =
        { {"title",        SIMPLE_CONTENT,  {.smpl = set_item_info} },
          {"creator",      SIMPLE_CONTENT,  {.smpl = set_item_info} },
175
          {"annotation",   SIMPLE_CONTENT,  {.smpl = set_item_info} },
176 177 178
          {"info",         SIMPLE_CONTENT,  {NULL} },
          {"location",     SIMPLE_CONTENT,  {NULL} },
          {"identifier",   SIMPLE_CONTENT,  {NULL} },
179
          {"image",        SIMPLE_CONTENT,  {.smpl = set_item_info} },
180 181 182 183 184
          {"date",         SIMPLE_CONTENT,  {NULL} },
          {"license",      SIMPLE_CONTENT,  {NULL} },
          {"attribution",  COMPLEX_CONTENT, {.cmplx = skip_element} },
          {"link",         SIMPLE_CONTENT,  {NULL} },
          {"meta",         SIMPLE_CONTENT,  {NULL} },
185
          {"extension",    COMPLEX_CONTENT, {.cmplx = parse_extension_node} },
186 187 188 189 190
          {"trackList",    COMPLEX_CONTENT, {.cmplx = parse_tracklist_node} },
          {NULL,           UNKNOWN_CONTENT, {NULL} }
        };

    /* read all playlist attributes */
191
    while( xml_ReaderNextAttr( p_xml_reader ) == VLC_SUCCESS )
192
    {
193 194 195
        psz_name = xml_ReaderName( p_xml_reader );
        psz_value = xml_ReaderValue( p_xml_reader );
        if( !psz_name || !psz_value )
196 197 198
        {
            msg_Err( p_demux, "invalid xml stream @ <playlist>" );
            FREE_ATT();
199
            return false;
200 201
        }
        /* attribute: version */
202
        if( !strcmp( psz_name, "version" ) )
Clément Stenac's avatar
Clément Stenac committed
203
        {
204
            b_version_found = true;
205
            if( strcmp( psz_value, "0" ) && strcmp( psz_value, "1" ) )
206 207 208
                msg_Warn( p_demux, "unsupported XSPF version" );
        }
        /* attribute: xmlns */
209
        else if( !strcmp( psz_name, "xmlns" ) || !strcmp( psz_name, "xmlns:vlc" ) )
210
            ;
Christophe Mutricy's avatar
Christophe Mutricy committed
211 212 213 214
        else if( !strcmp( psz_name, "xml:base" ) )
        {
            p_demux->p_sys->psz_base = decode_URI_duplicate( psz_value );
        }
215 216 217 218 219 220 221
        /* unknown attribute */
        else
            msg_Warn( p_demux, "invalid <playlist> attribute:\"%s\"", psz_name);

        FREE_ATT();
    }
    /* attribute version is mandatory !!! */
222
    if( !b_version_found )
223 224 225
        msg_Warn( p_demux, "<playlist> requires \"version\" attribute" );

    /* parse the child elements - we only take care of <trackList> */
226
    while( xml_ReaderRead( p_xml_reader ) == 1 )
227 228
    {
        i_node = xml_ReaderNodeType( p_xml_reader );
229
        switch( i_node )
230 231 232 233 234 235
        {
            case XML_READER_NONE:
                break;
            case XML_READER_STARTELEM:
                /*  element start tag  */
                psz_name = xml_ReaderName( p_xml_reader );
236
                if( !psz_name || !*psz_name )
237 238 239
                {
                    msg_Err( p_demux, "invalid xml stream" );
                    FREE_ATT();
240
                    return false;
241 242 243 244 245
                }
                /* choose handler */
                for( p_handler = pl_elements;
                     p_handler->name && strcmp( psz_name, p_handler->name );
                     p_handler++ );
246
                if( !p_handler->name )
247 248 249
                {
                    msg_Err( p_demux, "unexpected element <%s>", psz_name );
                    FREE_ATT();
250
                    return false;
251 252 253
                }
                FREE_NAME();
                /* complex content is parsed in a separate function */
254
                if( p_handler->type == COMPLEX_CONTENT )
255
                {
256
                    if( p_handler->pf_handler.cmplx( p_demux,
257
                                                     p_input_item,
258 259
                                                     p_xml_reader,
                                                     p_handler->name ) )
260 261 262 263 264 265 266
                    {
                        p_handler = NULL;
                        FREE_ATT();
                    }
                    else
                    {
                        FREE_ATT();
267
                        return false;
268 269 270 271 272 273 274 275
                    }
                }
                break;

            case XML_READER_TEXT:
                /* simple element content */
                FREE_ATT();
                psz_value = xml_ReaderValue( p_xml_reader );
276
                if( !psz_value )
277 278 279
                {
                    msg_Err( p_demux, "invalid xml stream" );
                    FREE_ATT();
280
                    return false;
281 282 283 284 285 286
                }
                break;

            case XML_READER_ENDELEM:
                /* element end tag */
                psz_name = xml_ReaderName( p_xml_reader );
287
                if( !psz_name )
288 289 290
                {
                    msg_Err( p_demux, "invalid xml stream" );
                    FREE_ATT();
291
                    return false;
292 293
                }
                /* leave if the current parent node <playlist> is terminated */
294
                if( !strcmp( psz_name, psz_element ) )
295 296
                {
                    FREE_ATT();
297
                    return true;
298 299
                }
                /* there MUST have been a start tag for that element name */
300 301
                if( !p_handler || !p_handler->name
                    || strcmp( p_handler->name, psz_name ))
302 303 304 305
                {
                    msg_Err( p_demux, "there's no open element left for <%s>",
                             psz_name );
                    FREE_ATT();
306
                    return false;
307 308
                }

309
                if( p_handler->pf_handler.smpl )
310
                {
311
                    p_handler->pf_handler.smpl( p_input_item, p_handler->name,
312 313 314 315 316 317 318 319 320 321
                                                psz_value );
                }
                FREE_ATT();
                p_handler = NULL;
                break;

            default:
                /* unknown/unexpected xml node */
                msg_Err( p_demux, "unexpected xml node %i", i_node );
                FREE_ATT();
322
                return false;
323 324 325
        }
        FREE_NAME();
    }
326
    return false;
327 328 329 330 331
}

/**
 * \brief parses the tracklist node which only may contain <track>s
 */
332
static bool parse_tracklist_node COMPLEX_INTERFACE
333
{
334
    VLC_UNUSED(psz_element);
335
    char *psz_name = NULL;
336 337 338 339
    int i_node;
    int i_ntracks = 0;

    /* now parse the <track>s */
340
    while( xml_ReaderRead( p_xml_reader ) == 1 )
341 342
    {
        i_node = xml_ReaderNodeType( p_xml_reader );
343
        if( i_node == XML_READER_STARTELEM )
344 345
        {
            psz_name = xml_ReaderName( p_xml_reader );
346
            if( !psz_name )
347 348 349
            {
                msg_Err( p_demux, "unexpected end of xml data" );
                FREE_NAME();
350
                return false;
351
            }
352
            if( strcmp( psz_name, "track") )
353 354 355 356
            {
                msg_Err( p_demux, "unexpected child of <trackList>: <%s>",
                         psz_name );
                FREE_NAME();
357
                return false;
358 359 360 361
            }
            FREE_NAME();

            /* parse the track data in a separate function */
362
            if( parse_track_node( p_demux, p_input_item,
363
                                   p_xml_reader,"track" ) == true )
364 365
                i_ntracks++;
        }
366
        else if( i_node == XML_READER_ENDELEM )
367 368 369 370
            break;
    }

    /* the <trackList> has to be terminated */
371
    if( xml_ReaderNodeType( p_xml_reader ) != XML_READER_ENDELEM )
372 373 374
    {
        msg_Err( p_demux, "there's a missing </trackList>" );
        FREE_NAME();
375
        return false;
376 377
    }
    psz_name = xml_ReaderName( p_xml_reader );
378
    if( !psz_name || strcmp( psz_name, "trackList" ) )
379 380 381
    {
        msg_Err( p_demux, "expected: </trackList>, found: </%s>", psz_name );
        FREE_NAME();
382
        return false;
383 384 385 386 387
    }
    FREE_NAME();

    msg_Dbg( p_demux, "parsed %i tracks successfully", i_ntracks );

388
    return true;
389 390 391 392 393 394
}

/**
 * \brief parse one track element
 * \param COMPLEX_INTERFACE
 */
395
static bool parse_track_node COMPLEX_INTERFACE
396 397
{
    int i_node;
398 399 400
    char *psz_name = NULL;
    char *psz_value = NULL;
    xml_elem_hnd_t *p_handler = NULL;
401 402 403 404 405 406

    xml_elem_hnd_t track_elements[] =
        { {"location",     SIMPLE_CONTENT,  {NULL} },
          {"identifier",   SIMPLE_CONTENT,  {NULL} },
          {"title",        SIMPLE_CONTENT,  {.smpl = set_item_info} },
          {"creator",      SIMPLE_CONTENT,  {.smpl = set_item_info} },
407
          {"annotation",   SIMPLE_CONTENT,  {.smpl = set_item_info} },
408
          {"info",         SIMPLE_CONTENT,  {NULL} },
409
          {"image",        SIMPLE_CONTENT,  {.smpl = set_item_info} },
410 411 412 413 414
          {"album",        SIMPLE_CONTENT,  {.smpl = set_item_info} },
          {"trackNum",     SIMPLE_CONTENT,  {.smpl = set_item_info} },
          {"duration",     SIMPLE_CONTENT,  {.smpl = set_item_info} },
          {"link",         SIMPLE_CONTENT,  {NULL} },
          {"meta",         SIMPLE_CONTENT,  {NULL} },
415
          {"extension",    COMPLEX_CONTENT, {.cmplx = parse_extension_node} },
416 417 418
          {NULL,           UNKNOWN_CONTENT, {NULL} }
        };

419 420 421 422 423 424 425 426
    input_item_t *p_new_input = input_item_NewExt( p_demux, NULL, NULL, 0, NULL, -1 );

    if( !p_new_input )
    {
        /* malloc has failed for input_item_NewExt, so bailout early */
        return false;
    }

427 428 429
    /* reset i_track_id */
    p_demux->p_sys->i_track_id = -1;

430
    while( xml_ReaderRead( p_xml_reader ) == 1 )
431 432
    {
        i_node = xml_ReaderNodeType( p_xml_reader );
433
        switch( i_node )
434 435 436 437 438 439 440
        {
            case XML_READER_NONE:
                break;

            case XML_READER_STARTELEM:
                /*  element start tag  */
                psz_name = xml_ReaderName( p_xml_reader );
441
                if( !psz_name || !*psz_name )
442 443 444
                {
                    msg_Err( p_demux, "invalid xml stream" );
                    FREE_ATT();
445
                    return false;
446 447 448 449 450
                }
                /* choose handler */
                for( p_handler = track_elements;
                     p_handler->name && strcmp( psz_name, p_handler->name );
                     p_handler++ );
451
                if( !p_handler->name )
452 453 454
                {
                    msg_Err( p_demux, "unexpected element <%s>", psz_name );
                    FREE_ATT();
455
                    return false;
456 457 458
                }
                FREE_NAME();
                /* complex content is parsed in a separate function */
459
                if( p_handler->type == COMPLEX_CONTENT )
460
                {
461
                    if( !p_new_input )
462 463 464 465 466
                    {
                        msg_Err( p_demux,
                                 "at <%s> level no new item has been allocated",
                                 p_handler->name );
                        FREE_ATT();
467
                        return false;
468
                    }
469
                    if( p_handler->pf_handler.cmplx( p_demux,
470
                                                     p_new_input,
471 472
                                                     p_xml_reader,
                                                     p_handler->name ) )
473 474 475 476 477 478 479
                    {
                        p_handler = NULL;
                        FREE_ATT();
                    }
                    else
                    {
                        FREE_ATT();
480
                        return false;
481 482 483 484 485 486 487 488
                    }
                }
                break;

            case XML_READER_TEXT:
                /* simple element content */
                FREE_ATT();
                psz_value = xml_ReaderValue( p_xml_reader );
489
                if( !psz_value )
490 491 492
                {
                    msg_Err( p_demux, "invalid xml stream" );
                    FREE_ATT();
493
                    return false;
494 495 496 497 498 499
                }
                break;

            case XML_READER_ENDELEM:
                /* element end tag */
                psz_name = xml_ReaderName( p_xml_reader );
500
                if( !psz_name )
501 502 503
                {
                    msg_Err( p_demux, "invalid xml stream" );
                    FREE_ATT();
504
                    return false;
505 506
                }
                /* leave if the current parent node <track> is terminated */
507
                if( !strcmp( psz_name, psz_element ) )
508 509
                {
                    FREE_ATT();
510 511 512

                    if( p_demux->p_sys->i_track_id < 0 )
                    {
513
                        input_item_AddSubItem( p_input_item, p_new_input );
514
                        vlc_gc_decref( p_new_input );
515 516 517 518
                        return true;
                    }

                    if( p_demux->p_sys->i_track_id >=
519
                           p_demux->p_sys->i_tracklist_entries )
Pierre d'Herbemont's avatar
Pierre d'Herbemont committed
520
                    {
521 522
                        input_item_t **pp;
                        pp = realloc( p_demux->p_sys->pp_tracklist,
523
                            (p_demux->p_sys->i_track_id + 1) * sizeof(*pp) );
524 525 526
                        if( !pp )
                            return false;
                        p_demux->p_sys->pp_tracklist = pp;
527
                        while( p_demux->p_sys->i_track_id >=
528 529
                               p_demux->p_sys->i_tracklist_entries )
                            pp[p_demux->p_sys->i_tracklist_entries++] = NULL;
530
                    }
531 532

                    p_demux->p_sys->pp_tracklist[
533
                            p_demux->p_sys->i_track_id ] = p_new_input;
534
                    return true;
535 536
                }
                /* there MUST have been a start tag for that element name */
537 538
                if( !p_handler || !p_handler->name
                    || strcmp( p_handler->name, psz_name ))
539 540 541 542
                {
                    msg_Err( p_demux, "there's no open element left for <%s>",
                             psz_name );
                    FREE_ATT();
543
                    return false;
544 545 546
                }

                /* special case: location */
547
                if( !strcmp( p_handler->name, "location" ) )
548
                {
549
                    char *psz_uri = NULL;
550
                    psz_uri = decode_URI_duplicate( psz_value );
Clément Stenac's avatar
Clément Stenac committed
551

552
                    if( !psz_uri )
553 554
                    {
                        FREE_ATT();
555
                        return false;
556
                    }
557 558

                    if( p_demux->p_sys->psz_base && !strstr( psz_uri, "://" ) )
559
                    {
560 561 562 563 564 565 566 567 568 569
                        char* psz_tmp;
                        if( asprintf( &psz_tmp, "%s%s", p_demux->p_sys->psz_base,
                                      psz_uri ) == -1 )
                        {
                            free( psz_uri );
                            FREE_ATT();
                            return NULL;
                        }
                        free( psz_uri );
                        psz_uri = psz_tmp;
570
                    }
571
                    input_item_SetURI( p_new_input, psz_uri );
572 573 574 575 576
                    free( psz_uri );
                    input_item_CopyOptions( p_input_item, p_new_input );
                    psz_uri = NULL;
                    FREE_ATT();
                    p_handler = NULL;
577 578 579 580
                }
                else
                {
                    /* there MUST be an item */
581
                    if( !p_new_input )
582
                    {
Clément Stenac's avatar
Clément Stenac committed
583
                        msg_Err( p_demux, "item not yet created at <%s>",
584 585
                                 psz_name );
                        FREE_ATT();
586
                        return false;
587
                    }
588
                    if( p_handler->pf_handler.smpl )
589
                    {
590
                        p_handler->pf_handler.smpl( p_new_input,
Clément Stenac's avatar
Clément Stenac committed
591
                                                    p_handler->name,
592 593 594 595 596 597 598 599 600 601 602 603
                                                    psz_value );
                        FREE_ATT();
                    }
                }
                FREE_ATT();
                p_handler = NULL;
                break;

            default:
                /* unknown/unexpected xml node */
                msg_Err( p_demux, "unexpected xml node %i", i_node );
                FREE_ATT();
604
                return false;
605 606 607 608 609
        }
        FREE_NAME();
    }
    msg_Err( p_demux, "unexpected end of xml data" );
    FREE_ATT();
610
    return false;
611 612 613 614 615
}

/**
 * \brief handles the supported <track> sub-elements
 */
616
static bool set_item_info SIMPLE_INTERFACE
617 618
{
    /* exit if setting is impossible */
619
    if( !psz_name || !psz_value || !p_input )
620
        return false;
621

622

623
    /* re-convert xml special characters inside psz_value */
624
    resolve_xml_special_chars( psz_value );
625 626

    /* handle each info element in a separate "if" clause */
627
    if( !strcmp( psz_name, "title" ) )
628
    {
629
        input_item_SetTitle( p_input, psz_value );
630
    }
631
    else if( !strcmp( psz_name, "creator" ) )
632
    {
633
        input_item_SetArtist( p_input, psz_value );
634
    }
635
    else if( !strcmp( psz_name, "album" ) )
636
    {
637
        input_item_SetAlbum( p_input, psz_value );
638

Clément Stenac's avatar
Clément Stenac committed
639
    }
640
    else if( !strcmp( psz_name, "trackNum" ) )
641
    {
642
        input_item_SetTrackNum( p_input, psz_value );
Clément Stenac's avatar
Clément Stenac committed
643
    }
644
    else if( !strcmp( psz_name, "duration" ) )
645 646
    {
        long i_num = atol( psz_value );
647
        input_item_SetDuration( p_input, (mtime_t) i_num*1000 );
648
    }
649 650
    else if( !strcmp( psz_name, "annotation" ) )
    {
651
        input_item_SetDescription( p_input, psz_value );
652
    }
653 654
    else if( !strcmp( psz_name, "image" ) )
    {
655
        char *psz_uri = decode_URI_duplicate( psz_value );
656 657
        input_item_SetArtURL( p_input, psz_uri );
        free( psz_uri );
658
    }
659
    return true;
660 661
}

662
/**
663
 * \brief handles the <vlc:option> elements
664 665 666 667 668 669 670 671 672
 */
static bool set_option SIMPLE_INTERFACE
{
    /* exit if setting is impossible */
    if( !psz_name || !psz_value || !p_input )
        return false;

    /* re-convert xml special characters inside psz_value */
    resolve_xml_special_chars( psz_value );
Antoine Cellerier's avatar
Antoine Cellerier committed
673

674
    input_item_AddOption( p_input, psz_value, 0 );
Antoine Cellerier's avatar
Antoine Cellerier committed
675

676 677
    return true;
}
678 679 680 681

/**
 * \brief parse the extension node of a XSPF playlist
 */
682
static bool parse_extension_node COMPLEX_INTERFACE
683 684 685
{
    char *psz_name = NULL;
    char *psz_value = NULL;
686
    char *psz_title = NULL;
687
    char *psz_application = NULL;
688
    int i_node;
689
    bool b_release_input_item = false;
690
    xml_elem_hnd_t *p_handler = NULL;
Yoann Peronneau's avatar
Yoann Peronneau committed
691
    input_item_t *p_new_input = NULL;
692 693

    xml_elem_hnd_t pl_elements[] =
694 695 696 697
        { {"vlc:node",   COMPLEX_CONTENT, {.cmplx = parse_extension_node} },
          {"vlc:item",   COMPLEX_CONTENT, {.cmplx = parse_extitem_node} },
          {"vlc:id",     SIMPLE_CONTENT, {NULL} },
          {"vlc:option", SIMPLE_CONTENT, {.smpl = set_option} },
698 699 700
          {NULL,    UNKNOWN_CONTENT, {NULL} }
        };

701 702 703 704 705 706 707
    /* read all extension node attributes */
    while( xml_ReaderNextAttr( p_xml_reader ) == VLC_SUCCESS )
    {
        psz_name = xml_ReaderName( p_xml_reader );
        psz_value = xml_ReaderValue( p_xml_reader );
        if( !psz_name || !psz_value )
        {
708
            msg_Err( p_demux, "invalid xml stream @ <vlc:node>" );
709
            FREE_ATT();
710
            return false;
711 712 713 714
        }
        /* attribute: title */
        if( !strcmp( psz_name, "title" ) )
        {
715 716
            resolve_xml_special_chars( psz_value );
            psz_title = strdup( psz_value );
717
        }
718
        /* extension attribute: application */
719
        else if( !strcmp( psz_name, "application" ) )
720 721 722
        {
            psz_application = strdup( psz_value );
        }
723 724
        /* unknown attribute */
        else
725 726
            msg_Warn( p_demux, "invalid <%s> attribute:\"%s\"", psz_element,
                      psz_name );
727 728 729 730 731

        FREE_ATT();
    }

    /* attribute title is mandatory except for <extension> */
732
    if( !strcmp( psz_element, "vlc:node" ) )
733
    {
Yoann Peronneau's avatar
Yoann Peronneau committed
734 735
        if( !psz_title )
        {
736
            msg_Warn( p_demux, "<vlc:node> requires \"title\" attribute" );
737
            return false;
Yoann Peronneau's avatar
Yoann Peronneau committed
738
        }
739 740 741
        p_new_input = input_item_NewWithType( VLC_OBJECT( p_demux ),
                          "vlc://nop", psz_title, 0, NULL, -1,
                          ITEM_TYPE_DIRECTORY );
Yoann Peronneau's avatar
Yoann Peronneau committed
742 743
        if( p_new_input )
        {
744
            input_item_AddSubItem( p_input_item, p_new_input );
Yoann Peronneau's avatar
Yoann Peronneau committed
745
            p_input_item = p_new_input;
746
            b_release_input_item = true;
Yoann Peronneau's avatar
Yoann Peronneau committed
747 748 749 750
        }
        free( psz_title );
    }
    else if( !strcmp( psz_element, "extension" ) )
751 752 753 754
    {
        if( !psz_application )
        {
            msg_Warn( p_demux, "<extension> requires \"application\" attribute" );
755
            return false;
756 757 758 759 760
        }
        else if( strcmp( psz_application, "http://www.videolan.org/vlc/playlist/0" ) )
        {
            msg_Dbg( p_demux, "Skipping \"%s\" extension tag", psz_application );
            free( psz_application );
761
            return false;
762 763 764
        }
    }
    free( psz_application );
765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780

    /* parse the child elements */
    while( xml_ReaderRead( p_xml_reader ) == 1 )
    {
        i_node = xml_ReaderNodeType( p_xml_reader );
        switch( i_node )
        {
            case XML_READER_NONE:
                break;
            case XML_READER_STARTELEM:
                /*  element start tag  */
                psz_name = xml_ReaderName( p_xml_reader );
                if( !psz_name || !*psz_name )
                {
                    msg_Err( p_demux, "invalid xml stream" );
                    FREE_ATT();
781
                    if( b_release_input_item ) vlc_gc_decref( p_new_input );
782
                    return false;
783 784 785 786 787 788 789 790 791
                }
                /* choose handler */
                for( p_handler = pl_elements;
                     p_handler->name && strcmp( psz_name, p_handler->name );
                     p_handler++ );
                if( !p_handler->name )
                {
                    msg_Err( p_demux, "unexpected element <%s>", psz_name );
                    FREE_ATT();
792
                    if( b_release_input_item ) vlc_gc_decref( p_new_input );
793
                    return false;
794 795 796 797 798 799
                }
                FREE_NAME();
                /* complex content is parsed in a separate function */
                if( p_handler->type == COMPLEX_CONTENT )
                {
                    if( p_handler->pf_handler.cmplx( p_demux,
800
                                                     p_input_item,
801 802 803 804 805 806 807 808 809
                                                     p_xml_reader,
                                                     p_handler->name ) )
                    {
                        p_handler = NULL;
                        FREE_ATT();
                    }
                    else
                    {
                        FREE_ATT();
810
                        if( b_release_input_item ) vlc_gc_decref( p_new_input );
811
                        return false;
812 813 814 815 816 817 818 819 820 821 822 823
                    }
                }
                break;

            case XML_READER_TEXT:
                /* simple element content */
                FREE_ATT();
                psz_value = xml_ReaderValue( p_xml_reader );
                if( !psz_value )
                {
                    msg_Err( p_demux, "invalid xml stream" );
                    FREE_ATT();
824
                    if( b_release_input_item ) vlc_gc_decref( p_new_input );
825
                    return false;
826 827 828 829 830 831 832 833 834 835
                }
                break;

            case XML_READER_ENDELEM:
                /* element end tag */
                psz_name = xml_ReaderName( p_xml_reader );
                if( !psz_name )
                {
                    msg_Err( p_demux, "invalid xml stream" );
                    FREE_ATT();
836
                    if( b_release_input_item ) vlc_gc_decref( p_new_input );
837
                    return false;
838 839 840 841 842
                }
                /* leave if the current parent node is terminated */
                if( !strcmp( psz_name, psz_element ) )
                {
                    FREE_ATT();
843
                    if( b_release_input_item ) vlc_gc_decref( p_new_input );
844
                    return true;
845 846 847 848 849 850
                }
                /* there MUST have been a start tag for that element name */
                if( !p_handler || !p_handler->name
                    || strcmp( p_handler->name, psz_name ))
                {
                    msg_Err( p_demux, "there's no open element left for <%s>",
851
                             psz_name );
852
                    FREE_ATT();
853
                    if( b_release_input_item ) vlc_gc_decref( p_new_input );
854
                    return false;
855 856
                }

857 858 859 860 861 862
                /* special tag <vlc:id> */
                if( !strcmp( p_handler->name, "vlc:id" ) )
                {
                    p_demux->p_sys->i_track_id = atoi( psz_value );
                }
                else if( p_handler->pf_handler.smpl )
863
                {
864
                    p_handler->pf_handler.smpl( p_input_item, p_handler->name,
865 866 867 868 869 870 871 872 873 874
                                                psz_value );
                }
                FREE_ATT();
                p_handler = NULL;
                break;

            default:
                /* unknown/unexpected xml node */
                msg_Err( p_demux, "unexpected xml node %i", i_node );
                FREE_ATT();
875
                if( b_release_input_item ) vlc_gc_decref( p_new_input );
876
                return false;
877 878 879
        }
        FREE_NAME();
    }
880
    if( b_release_input_item ) vlc_gc_decref( p_new_input );
881
    return false;
882 883 884 885 886
}

/**
 * \brief parse the extension item node of a XSPF playlist
 */
887
static bool parse_extitem_node COMPLEX_INTERFACE
888
{
889
    VLC_UNUSED(psz_element);
Yoann Peronneau's avatar
Yoann Peronneau committed