src/playlist/loadsave.c, modules/demux/playlist/*, modules/gui/*,
authorAntoine Cellerier <dionoea@videolan.org>
Sat, 11 Mar 2006 23:19:59 +0000 (23:19 +0000)
committerAntoine Cellerier <dionoea@videolan.org>
Sat, 11 Mar 2006 23:19:59 +0000 (23:19 +0000)
modules/misc/playlist/*, modules/access/http.c: XSPF playlist support
(read and write) by Daniel Stranger. Many thanks

src/misc/modules.c, src/misc/strings.c, include/vlc_strings.h: string
handling functions.

modules/control/http/*, modules/services_discovery/upnp_intel.cpp: use
these string handling functions.

21 files changed:
include/vlc_strings.h [new file with mode: 0644]
include/vlc_symbols.h
modules/access/http.c
modules/control/http/rpn.c
modules/control/http/util.c
modules/demux/playlist/Modules.am
modules/demux/playlist/playlist.c
modules/demux/playlist/playlist.h
modules/demux/playlist/xspf.c [new file with mode: 0644]
modules/demux/playlist/xspf.h [new file with mode: 0644]
modules/gui/skins2/src/dialogs.cpp
modules/gui/wxwidgets/dialogs/playlist.cpp
modules/misc/playlist/Modules.am
modules/misc/playlist/export.c
modules/misc/playlist/xspf.c [new file with mode: 0644]
modules/misc/playlist/xspf.h [new file with mode: 0644]
modules/services_discovery/upnp_intel.cpp
src/Makefile.am
src/misc/modules.c
src/misc/strings.c [new file with mode: 0644]
src/playlist/loadsave.c

diff --git a/include/vlc_strings.h b/include/vlc_strings.h
new file mode 100644 (file)
index 0000000..c70a5ed
--- /dev/null
@@ -0,0 +1,44 @@
+/*****************************************************************************
+ * vlc_strings.h: String functions
+ *****************************************************************************
+ * Copyright (C) 2006 the VideoLAN team
+ * $Id$
+ *
+ * Authors: Antoine Cellerier <dionoea at videolan dot org>
+ *
+ * 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.
+ *****************************************************************************/
+
+#ifndef _VLC_STRINGS_H
+#define _VLC_STRINGS_H 1
+
+#include <vlc/vlc.h>
+
+/**
+ * \defgroup strings Strings
+ * @{
+ */
+
+VLC_EXPORT( char *, decode_encoded_URI_duplicate, ( const char *psz ) );
+VLC_EXPORT( void, decode_encoded_URI, ( char *psz ) );
+
+VLC_EXPORT( void, resolve_xml_special_chars, ( char *psz_value ) );
+VLC_EXPORT( char *, convert_xml_special_chars, ( const char *psz_content ) );
+
+/**
+ * @}
+ */
+
+#endif
index 8099fb4..7a756ee 100644 (file)
@@ -480,6 +480,10 @@ struct module_symbols_t
     double (*us_atof_inner) (const char *);
     double (*us_strtod_inner) (const char *, char **);
     lldiv_t (*vlc_lldiv_inner) (long long numer, long long denom);
+    void (*decode_encoded_URI_inner) (char *psz);
+    char * (*convert_xml_special_chars_inner) (const char *psz_content);
+    char * (*decode_encoded_URI_duplicate_inner) (const char *psz);
+    void (*resolve_xml_special_chars_inner) (char *psz_value);
 };
 # if defined (__PLUGIN__)
 #  define aout_FiltersCreatePipeline (p_symbols)->aout_FiltersCreatePipeline_inner
@@ -940,6 +944,10 @@ struct module_symbols_t
 #  define us_atof (p_symbols)->us_atof_inner
 #  define us_strtod (p_symbols)->us_strtod_inner
 #  define vlc_lldiv (p_symbols)->vlc_lldiv_inner
+#  define decode_encoded_URI (p_symbols)->decode_encoded_URI_inner
+#  define convert_xml_special_chars (p_symbols)->convert_xml_special_chars_inner
+#  define decode_encoded_URI_duplicate (p_symbols)->decode_encoded_URI_duplicate_inner
+#  define resolve_xml_special_chars (p_symbols)->resolve_xml_special_chars_inner
 # elif defined (HAVE_DYNAMIC_PLUGINS) && !defined (__BUILTIN__)
 /******************************************************************
  * STORE_SYMBOLS: store VLC APIs into p_symbols for plugin access.
@@ -1403,6 +1411,10 @@ struct module_symbols_t
     ((p_symbols)->us_atof_inner) = us_atof; \
     ((p_symbols)->us_strtod_inner) = us_strtod; \
     ((p_symbols)->vlc_lldiv_inner) = vlc_lldiv; \
+    ((p_symbols)->decode_encoded_URI_inner) = decode_encoded_URI; \
+    ((p_symbols)->convert_xml_special_chars_inner) = convert_xml_special_chars; \
+    ((p_symbols)->decode_encoded_URI_duplicate_inner) = decode_encoded_URI_duplicate; \
+    ((p_symbols)->resolve_xml_special_chars_inner) = resolve_xml_special_chars; \
     (p_symbols)->net_ConvertIPv4_deprecated = NULL; \
     (p_symbols)->__stats_CounterGet_deprecated = NULL; \
     (p_symbols)->__stats_TimerDumpAll_deprecated = NULL; \
index 102770f..fc62ba2 100644 (file)
@@ -403,6 +403,9 @@ connect:
         /* Grrrr! detect ultravox server and force NSV demuxer */
         p_access->psz_demux = strdup( "nsv" );
     }
+    else if( p_sys->psz_mime &&
+             !strcasecmp( p_sys->psz_mime, "application/xspf+xml" ) )
+        p_access->psz_demux = strdup( "xspf-open" );
 
     if( p_sys->b_reconnect ) msg_Dbg( p_access, "auto re-connect enabled" );
 
index b21b3eb..3930027 100644 (file)
@@ -420,44 +420,8 @@ void E_(EvaluateRPN)( intf_thread_t *p_intf, mvar_t  *vars,
         {
             char *psz_src = E_(SSPop)( st );
             char *psz_dest;
-            char *str = psz_src;
-
-            p = psz_dest = malloc( strlen( str ) * 6 + 1 );
 
-            while( *str != '\0' )
-            {
-                if( *str == '&' )
-                {
-                    strcpy( p, "&amp;" );
-                    p += 5;
-                }
-                else if( *str == '\"' )
-                {
-                    strcpy( p, "&quot;" );
-                    p += 6;
-                }
-                else if( *str == '\'' )
-                {
-                    strcpy( p, "&#039;" );
-                    p += 6;
-                }
-                else if( *str == '<' )
-                {
-                    strcpy( p, "&lt;" );
-                    p += 4;
-                }
-                else if( *str == '>' )
-                {
-                    strcpy( p, "&gt;" );
-                    p += 4;
-                }
-                else
-                {
-                    *p++ = *str;
-                }
-                str++;
-            }
-            *p = '\0';
+            psz_dest = convert_xml_special_chars( psz_src );
 
             E_(SSPush)( st, psz_dest );
             free( psz_src );
index 69b469c..9d9ddce 100644 (file)
@@ -24,6 +24,7 @@
  *****************************************************************************/
 
 #include "http.h"
+#include "vlc_strings.h"
 
 /****************************************************************************
  * File and directory functions
@@ -747,38 +748,7 @@ char *E_(ExtractURIValue)( char *psz_uri, const char *psz_name,
 
 void E_(DecodeEncodedURI)( char *psz )
 {
-    char *dup = strdup( psz );
-    char *p = dup;
-
-    while( *p )
-    {
-        if( *p == '%' )
-        {
-            char val[3];
-            p++;
-            if( !*p )
-            {
-                break;
-            }
-
-            val[0] = *p++;
-            val[1] = *p++;
-            val[2] = '\0';
-
-            *psz++ = strtol( val, NULL, 16 );
-        }
-        else if( *p == '+' )
-        {
-            *psz++ = ' ';
-            p++;
-        }
-        else
-        {
-            *psz++ = *p++;
-        }
-    }
-    *psz++ = '\0';
-    free( dup );
+    decode_encoded_URI( psz );
 }
 
 /* Since the resulting string is smaller we can work in place, so it is
index 8b730df..e20523c 100644 (file)
@@ -1,8 +1,14 @@
-SOURCES_playlist = playlist.c \
-                  playlist.h \
-                  old.c \
-                  m3u.c \
-                   b4s.c \
-                  pls.c \
-                  dvb.c \
-                  podcast.c 
+SOURCES_playlist = \
+       playlist.c \
+       playlist.h \
+       old.c \
+       m3u.c \
+       b4s.c \
+       pls.c \
+       dvb.c \
+       podcast.c \
+       xspf.c \
+       xspf.h \
+       $(NULL)
+
+
index 13cff3b..8b07771 100644 (file)
@@ -84,6 +84,11 @@ vlc_module_begin();
         add_shortcut( "podcast" );
         set_capability( "demux2", 10 );
         set_callbacks( E_(Import_podcast), E_(Close_podcast) );
+    add_submodule();
+        set_description( _("XSPF playlist import") );
+        add_shortcut( "xspf-open" );
+        set_capability( "demux2", 10 );
+        set_callbacks( E_(xspf_import_Activate), NULL );
 vlc_module_end();
 
 
index e96b55d..2eba68c 100644 (file)
@@ -45,3 +45,5 @@ void E_(Close_DVB) ( vlc_object_t * );
 
 int E_(Import_podcast) ( vlc_object_t * );
 void E_(Close_podcast) ( vlc_object_t * );
+
+int E_(xspf_import_Activate) ( vlc_object_t * );
diff --git a/modules/demux/playlist/xspf.c b/modules/demux/playlist/xspf.c
new file mode 100644 (file)
index 0000000..670785a
--- /dev/null
@@ -0,0 +1,682 @@
+/******************************************************************************
+ * Copyright (C) 2006 Daniel Stränger <vlc at schmaller dot de>
+ *
+ * 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.
+ *******************************************************************************/
+/**
+ * \file modules/demux/playlist/xspf.c
+ * \brief XSPF playlist import functions
+ */
+
+#include <vlc/vlc.h>
+#include <vlc/input.h>
+#include <vlc/intf.h>
+
+#include "playlist.h"
+#include "vlc_xml.h"
+#include "vlc_strings.h"
+#include "xspf.h"
+
+/**
+ * \brief XSPF submodule initialization function
+ */
+int E_(xspf_import_Activate)( vlc_object_t *p_this )
+{
+    demux_t *p_demux = (demux_t *)p_this;
+    char    *psz_ext;
+
+    psz_ext = strrchr ( p_demux->psz_path, '.' );
+
+    if( ( psz_ext && !strcasecmp( psz_ext, ".xspf") ) ||
+        ( p_demux->psz_demux && !strcmp(p_demux->psz_demux, "xspf-open") ) )
+    {
+        ;
+    }
+    else
+    {
+        return VLC_EGENERIC;
+    }
+    msg_Dbg( p_demux, "using xspf playlist import");
+
+    p_demux->pf_control = xspf_import_Control;
+    p_demux->pf_demux = xspf_import_Demux;
+
+    return VLC_SUCCESS;
+}
+
+/**
+ * \brief demuxer function for XSPF parsing
+ */
+int xspf_import_Demux( demux_t *p_demux )
+{
+    playlist_t *p_playlist = NULL;
+    playlist_item_t *p_current = NULL;
+
+    vlc_bool_t b_play;
+    int i_ret = VLC_SUCCESS;
+
+    xml_t *p_xml = NULL;
+    xml_reader_t *p_xml_reader = NULL;
+    char *psz_name = NULL;
+
+    /* create new xml parser from stream */
+    p_xml = xml_Create( p_demux );
+    if( !p_xml )
+        i_ret = VLC_ENOMOD;
+    else
+    {
+        p_xml_reader = xml_ReaderCreate( p_xml, p_demux->s );
+        if( !p_xml_reader )
+            i_ret = VLC_EGENERIC;
+    }
+
+    /* start with parsing the root node */
+    if ( i_ret == VLC_SUCCESS )
+        if ( xml_ReaderRead( p_xml_reader ) != 1 )
+        {
+            msg_Err( p_demux, "can't read xml stream" );
+            i_ret = VLC_EGENERIC;
+        }
+    /* checking root nody type */
+    if ( i_ret == VLC_SUCCESS )
+        if( xml_ReaderNodeType( p_xml_reader ) != XML_READER_STARTELEM )
+        {
+            msg_Err( p_demux, "invalid root node type: %i", xml_ReaderNodeType( p_xml_reader ) );
+            i_ret = VLC_EGENERIC;
+        }
+    /* checking root node name */
+    if ( i_ret == VLC_SUCCESS )
+        psz_name = xml_ReaderName( p_xml_reader );
+    if ( !psz_name || strcmp( psz_name, "playlist" ) )
+    {
+        msg_Err( p_demux, "invalid root node name: %s", psz_name );
+        i_ret = VLC_EGENERIC;
+    }
+    FREE_NAME();
+
+    /* get the playlist ... */
+    if ( i_ret == VLC_SUCCESS )
+    {
+        p_playlist = (playlist_t *) vlc_object_find( p_demux, VLC_OBJECT_PLAYLIST, FIND_PARENT );
+        if( !p_playlist )
+        {
+            msg_Err( p_demux, "can't find playlist" );
+            i_ret = VLC_ENOOBJ;
+        }
+    }
+    /* ... and its current item (to convert it to a node) */
+    if ( i_ret == VLC_SUCCESS )
+    {
+        b_play = E_(FindItem)( p_demux, p_playlist, &p_current );
+        playlist_ItemToNode( p_playlist, p_current );
+        p_current->input.i_type = ITEM_TYPE_PLAYLIST;
+        /* parse the playlist node */
+        i_ret = parse_playlist_node( p_demux, p_playlist, p_current,
+                                     p_xml_reader, "playlist" );
+        /* true/false - success/egeneric mapping */
+        i_ret = ( i_ret==VLC_TRUE ? VLC_SUCCESS : VLC_EGENERIC );
+
+        if( b_play )
+        {
+            playlist_Control( p_playlist, PLAYLIST_VIEWPLAY,
+                              p_playlist->status.i_view,
+                              p_playlist->status.p_item, NULL );
+        }
+    }
+
+    if ( p_playlist )
+        vlc_object_release( p_playlist );
+    if ( p_xml_reader )
+        xml_ReaderDelete( p_xml, p_xml_reader );
+    if ( p_xml )
+        xml_Delete( p_xml );
+
+    return i_ret;
+}
+
+/** \brief dummy function for demux callback interface */
+int xspf_import_Control( demux_t *p_demux, int i_query, va_list args )
+{
+    return VLC_EGENERIC;
+}
+
+/**
+ * \brief parse the root node of a XSPF playlist
+ * \param p_demux demuxer instance
+ * \param p_playlist playlist instance
+ * \param p_item current playlist node
+ * \param p_xml_reader xml reader instance
+ * \param psz_element name of element to parse
+ */
+static vlc_bool_t parse_playlist_node COMPLEX_INTERFACE
+{
+    char *psz_name=NULL;
+    char *psz_value=NULL;
+    vlc_bool_t b_version_found = VLC_FALSE;
+    int i_node;
+    xml_elem_hnd_t *p_handler=NULL;
+
+    xml_elem_hnd_t pl_elements[] =
+        { {"title",        SIMPLE_CONTENT,  {.smpl = set_item_info} },
+          {"creator",      SIMPLE_CONTENT,  {.smpl = set_item_info} },
+          {"annotation",   SIMPLE_CONTENT,  {NULL} },
+          {"info",         SIMPLE_CONTENT,  {NULL} },
+          {"location",     SIMPLE_CONTENT,  {NULL} },
+          {"identifier",   SIMPLE_CONTENT,  {NULL} },
+          {"image",        SIMPLE_CONTENT,  {NULL} },
+          {"date",         SIMPLE_CONTENT,  {NULL} },
+          {"license",      SIMPLE_CONTENT,  {NULL} },
+          {"attribution",  COMPLEX_CONTENT, {.cmplx = skip_element} },
+          {"link",         SIMPLE_CONTENT,  {NULL} },
+          {"meta",         SIMPLE_CONTENT,  {NULL} },
+          {"extension",    COMPLEX_CONTENT, {.cmplx = skip_element} },
+          {"trackList",    COMPLEX_CONTENT, {.cmplx = parse_tracklist_node} },
+          {NULL,           UNKNOWN_CONTENT, {NULL} }
+        };
+
+    /* read all playlist 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 )
+        {
+            msg_Err( p_demux, "invalid xml stream @ <playlist>" );
+            FREE_ATT();
+            return VLC_FALSE;
+        }
+        /* attribute: version */
+        if ( !strcmp( psz_name, "version" ) )
+        {
+            b_version_found = VLC_TRUE;
+            if ( strcmp( psz_value, "0" ) && strcmp( psz_value, "1" ) )
+                msg_Warn( p_demux, "unsupported XSPF version" );
+        }
+        /* attribute: xmlns */
+        else if ( !strcmp ( psz_name, "xmlns" ) )
+            ;
+        /* unknown attribute */
+        else
+            msg_Warn( p_demux, "invalid <playlist> attribute:\"%s\"", psz_name);
+
+        FREE_ATT();
+    }
+    /* attribute version is mandatory !!! */
+    if ( !b_version_found )
+        msg_Warn( p_demux, "<playlist> requires \"version\" attribute" );
+
+    /* parse the child elements - we only take care of <trackList> */
+    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();
+                    return VLC_FALSE;
+                }
+                /* 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();
+                    return VLC_FALSE;
+                }
+                FREE_NAME();
+                /* complex content is parsed in a separate function */
+                if ( p_handler->type == COMPLEX_CONTENT )
+                {
+                    if ( p_handler->pf_handler.cmplx( p_demux,
+                                                      p_playlist,
+                                                      p_item,
+                                                      p_xml_reader,
+                                                      p_handler->name ) )
+                    {
+                        p_handler = NULL;
+                        FREE_ATT();
+                    }
+                    else
+                    {
+                        FREE_ATT();
+                        return VLC_FALSE;
+                    }
+                }
+                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();
+                    return VLC_FALSE;
+                }
+                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();
+                    return VLC_FALSE;
+                }
+                /* leave if the current parent node <playlist> is terminated */
+                if ( !strcmp( psz_name, psz_element ) )
+                {
+                    FREE_ATT();
+                    return VLC_TRUE;
+                }
+                /* 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>",
+                             psz_name );
+                    FREE_ATT();
+                    return VLC_FALSE;
+                }
+
+                if ( p_handler->pf_handler.smpl )
+                {
+                    p_handler->pf_handler.smpl( p_item, p_handler->name,
+                                                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();
+                return VLC_FALSE;
+        }
+        FREE_NAME();
+    }
+    return VLC_FALSE;
+}
+
+/**
+ * \brief parses the tracklist node which only may contain <track>s
+ */
+static vlc_bool_t parse_tracklist_node COMPLEX_INTERFACE
+{
+    char *psz_name=NULL;
+    int i_node;
+    int i_ntracks = 0;
+
+    /* now parse the <track>s */
+    while ( xml_ReaderRead( p_xml_reader ) == 1 )
+    {
+        i_node = xml_ReaderNodeType( p_xml_reader );
+        if ( i_node == XML_READER_STARTELEM )
+        {
+            psz_name = xml_ReaderName( p_xml_reader );
+            if ( !psz_name )
+            {
+                msg_Err( p_demux, "unexpected end of xml data" );
+                FREE_NAME();
+                return VLC_FALSE;
+            }
+            if ( strcmp( psz_name, "track") )
+            {
+                msg_Err( p_demux, "unexpected child of <trackList>: <%s>",
+                         psz_name );
+                FREE_NAME();
+                return VLC_FALSE;
+            }
+            FREE_NAME();
+
+            /* parse the track data in a separate function */
+            if ( parse_track_node( p_demux, p_playlist, p_item, p_xml_reader,
+                                   "track" ) == VLC_TRUE )
+                i_ntracks++;
+        }
+        else if ( i_node == XML_READER_ENDELEM )
+            break;
+    }
+
+    /* the <trackList> has to be terminated */
+    if ( xml_ReaderNodeType( p_xml_reader ) != XML_READER_ENDELEM )
+    {
+        msg_Err( p_demux, "there's a missing </trackList>" );
+        FREE_NAME();
+        return VLC_FALSE;
+    }
+    psz_name = xml_ReaderName( p_xml_reader );
+    if ( !psz_name || strcmp( psz_name, "trackList" ) )
+    {
+        msg_Err( p_demux, "expected: </trackList>, found: </%s>", psz_name );
+        FREE_NAME();
+        return VLC_FALSE;
+    }
+    FREE_NAME();
+
+    msg_Dbg( p_demux, "parsed %i tracks successfully", i_ntracks );
+
+    return VLC_TRUE;
+}
+
+/**
+ * \brief parse one track element
+ * \param COMPLEX_INTERFACE
+ */
+static vlc_bool_t parse_track_node COMPLEX_INTERFACE
+{
+    playlist_item_t *p_new=NULL;
+    int i_node;
+    char *psz_name=NULL;
+    char *psz_value=NULL;
+    xml_elem_hnd_t *p_handler=NULL;
+
+    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} },
+          {"annotation",   SIMPLE_CONTENT,  {NULL} },
+          {"info",         SIMPLE_CONTENT,  {NULL} },
+          {"image",        SIMPLE_CONTENT,  {NULL} },
+          {"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} },
+          {"extension",    COMPLEX_CONTENT, {.cmplx = skip_element} },
+          {NULL,           UNKNOWN_CONTENT, {NULL} }
+        };
+
+    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();
+                    return VLC_FALSE;
+                }
+                /* choose handler */
+                for( p_handler = track_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();
+                    return VLC_FALSE;
+                }
+                FREE_NAME();
+                /* complex content is parsed in a separate function */
+                if ( p_handler->type == COMPLEX_CONTENT )
+                {
+                    if ( !p_new )
+                    {
+                        msg_Err( p_demux,
+                                 "at <%s> level no new item has been allocated",
+                                 p_handler->name );
+                        FREE_ATT();
+                        return VLC_FALSE;
+                    }
+                    if ( p_handler->pf_handler.cmplx( p_demux,
+                                                      p_playlist,
+                                                      p_new,
+                                                      p_xml_reader,
+                                                      p_handler->name ) )
+                    {
+                        p_handler = NULL;
+                        FREE_ATT();
+                    }
+                    else
+                    {
+                        FREE_ATT();
+                        return VLC_FALSE;
+                    }
+                }
+                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();
+                    return VLC_FALSE;
+                }
+                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();
+                    return VLC_FALSE;
+                }
+                /* leave if the current parent node <track> is terminated */
+                if ( !strcmp( psz_name, psz_element ) )
+                {
+                    FREE_ATT();
+                    return VLC_TRUE;
+                }
+                /* 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>",
+                             psz_name );
+                    FREE_ATT();
+                    return VLC_FALSE;
+                }
+
+                /* special case: location */
+                if ( !strcmp( p_handler->name, "location" ) )
+                {
+                    /* there MUST NOT be an item */
+                    if ( p_new )
+                    {
+                        msg_Err( p_demux,
+                                 "a new item has just been created <%s>",
+                                 psz_name );
+                        FREE_ATT();
+                        return VLC_FALSE;
+                    }
+                    /* create it now */
+                    if ( insert_new_item( p_playlist, p_item,
+                                          &p_new, psz_value ) )
+                    {
+                        FREE_ATT();
+                        p_handler = NULL;
+                    }
+                    else
+                    {
+                        FREE_ATT();
+                        return VLC_FALSE;
+                    }
+                }
+                else
+                {
+                    /* there MUST be an item */
+                    if ( !p_new )
+                    {
+                        msg_Err( p_demux,
+                                 "an item hasn't been created yet <%s>",
+                                 psz_name );
+                        FREE_ATT();
+                        return VLC_FALSE;
+                    }
+                    if ( p_handler->pf_handler.smpl )
+                    {
+                        p_handler->pf_handler.smpl( p_new, p_handler->name,
+                                                    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();
+                return VLC_FALSE;
+        }
+        FREE_NAME();
+    }
+    msg_Err( p_demux, "unexpected end of xml data" );
+    FREE_ATT();
+    return VLC_FALSE;
+}
+
+/**
+ * \brief handles the supported <track> sub-elements
+ */
+static vlc_bool_t set_item_info SIMPLE_INTERFACE
+{
+    /* exit if setting is impossible */
+    if ( !psz_name || !psz_value || !p_item )
+        return VLC_FALSE;
+
+    /* re-convert xml special characters inside psz_value */
+    resolve_xml_special_chars ( psz_value );
+
+    /* handle each info element in a separate "if" clause */
+    if ( !strcmp( psz_name, "title" ) )
+    {
+        if ( playlist_ItemSetName ( p_item, (char *)psz_value ) == VLC_SUCCESS )
+            return VLC_TRUE;
+        return VLC_FALSE;
+    }
+    else if ( !strcmp( psz_name, "creator" ) )
+    {
+        if ( vlc_input_item_AddInfo( &(p_item->input),
+                                     _(VLC_META_INFO_CAT), _(VLC_META_ARTIST),
+                                     "%s", psz_value ) == VLC_SUCCESS )
+            return VLC_TRUE;
+        return VLC_FALSE;
+
+    }
+    else if ( !strcmp( psz_name, "album" ) )
+    {
+        if ( vlc_input_item_AddInfo( &(p_item->input),
+                                     _(VLC_META_INFO_CAT),
+                                     _(VLC_META_COLLECTION),
+                                     "%s", psz_value ) == VLC_SUCCESS )
+            return VLC_TRUE;
+        return VLC_FALSE;
+
+    } else if ( !strcmp( psz_name, "trackNum" ) )
+    {
+        long i_num = atol( psz_value );
+        if ( i_num > 0
+             && vlc_input_item_AddInfo( &(p_item->input),
+                                         _(VLC_META_INFO_CAT),
+                                         _(VLC_META_SEQ_NUM),
+                                         "%s", psz_value ) == VLC_SUCCESS )
+                return VLC_TRUE;
+        return VLC_FALSE;
+
+    } else if ( !strcmp( psz_name, "duration" ) )
+    {
+        long i_num = atol( psz_value );
+        if ( i_num > 0
+             && playlist_ItemSetDuration( p_item, i_num*1000 ) == VLC_SUCCESS )
+                return VLC_TRUE;
+        return VLC_FALSE;
+
+    }
+
+    return VLC_TRUE;
+}
+
+/**
+ * \brief skips complex element content that we can't manage
+ */
+static vlc_bool_t skip_element COMPLEX_INTERFACE
+{
+    char *psz_endname;
+
+    while ( xml_ReaderRead( p_xml_reader ) == 1 )
+    {
+        if ( xml_ReaderNodeType( p_xml_reader ) == XML_READER_ENDELEM )
+        {
+            psz_endname = xml_ReaderName( p_xml_reader );
+            if ( !psz_endname )
+                return VLC_FALSE;
+            if ( !strcmp( psz_element, psz_endname ) )
+            {
+                free( psz_endname );
+                return VLC_TRUE;
+            }
+            else
+                free( psz_endname );
+        }
+    }
+    return VLC_FALSE;
+}
+
+/**
+ * \brief creates a new playlist item from the given mrl
+ */
+static vlc_bool_t insert_new_item( playlist_t *p_pl, playlist_item_t *p_cur,
+                                   playlist_item_t **pp_new, char *psz_location )
+{
+    char *psz_uri=NULL;
+    psz_uri = decode_encoded_URI_duplicate( psz_location );
+
+    if ( psz_uri )
+    {
+        *pp_new = playlist_ItemNew( p_pl, psz_uri, NULL );
+        free( psz_uri );
+        psz_uri = NULL;
+    }
+
+    if ( !*pp_new )
+        return VLC_FALSE;
+
+    playlist_NodeAddItem( p_pl,  *pp_new,         p_cur->pp_parents[0]->i_view,
+                          p_cur, PLAYLIST_APPEND, PLAYLIST_END );
+
+    playlist_CopyParents( p_cur, *pp_new );
+
+    vlc_input_item_CopyOptions( &p_cur->input, &((*pp_new)->input) );
+
+    return VLC_TRUE;
+}
diff --git a/modules/demux/playlist/xspf.h b/modules/demux/playlist/xspf.h
new file mode 100644 (file)
index 0000000..b64e37c
--- /dev/null
@@ -0,0 +1,62 @@
+/*****************************************************************************
+ * Copyright (C) 2006 Daniel Stränger <vlc at schmaller dot de>
+ *
+ * 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.
+ *******************************************************************************/
+/**
+ * \file modules/demux/playlist/xspf.h
+ * \brief XSPF playlist import: prototypes, datatypes, defines
+ */
+
+/* defines */
+#define FREE_NAME()  if (psz_name) {free(psz_name);psz_name=NULL;}
+#define FREE_VALUE() if (psz_value) {free(psz_value);psz_value=NULL;}
+#define FREE_ATT()   FREE_NAME();FREE_VALUE()
+
+#define UNKNOWN_CONTENT 0
+#define SIMPLE_CONTENT 1
+#define COMPLEX_CONTENT 2
+
+#define SIMPLE_INTERFACE  (playlist_item_t *p_item,\
+                           const char      *psz_name,\
+                           char            *psz_value)
+#define COMPLEX_INTERFACE (demux_t         *p_demux,\
+                           playlist_t      *p_playlist,\
+                           playlist_item_t *p_item,\
+                           xml_reader_t    *p_xml_reader,\
+                           const char      *psz_element)
+
+/* prototypes */
+int xspf_import_Demux( demux_t *);
+int xspf_import_Control( demux_t *, int, va_list );
+
+static vlc_bool_t parse_playlist_node COMPLEX_INTERFACE;
+static vlc_bool_t parse_tracklist_node COMPLEX_INTERFACE;
+static vlc_bool_t parse_track_node COMPLEX_INTERFACE;
+static vlc_bool_t set_item_info SIMPLE_INTERFACE;
+static vlc_bool_t skip_element COMPLEX_INTERFACE;
+static vlc_bool_t insert_new_item( playlist_t *, playlist_item_t *, playlist_item_t **, char *);
+
+/* datatypes */
+typedef struct
+{
+    const char *name;
+    int type;
+    union
+    {
+        vlc_bool_t (*smpl) SIMPLE_INTERFACE;
+        vlc_bool_t (*cmplx) COMPLEX_INTERFACE;
+    } pf_handler;
+} xml_elem_hnd_t;
index 1e60680..40c8dd1 100644 (file)
@@ -229,14 +229,16 @@ void Dialogs::showChangeSkin()
 void Dialogs::showPlaylistLoad()
 {
     showFileGeneric( _("Open playlist"),
-                     _("All playlists|*.pls;*.m3u;*.asx;*.b4s|M3U files|*.m3u"),
+                     _("All playlists|*.pls;*.m3u;*.asx;*.b4s;*.xspf|"
+                       "M3U files|*.m3u|"
+                       "XSPF playlist|*.xspf"),
                      showPlaylistLoadCB, kOPEN );
 }
 
 
 void Dialogs::showPlaylistSave()
 {
-    showFileGeneric( _("Save playlist"), _("M3U file|*.m3u"),
+    showFileGeneric( _("Save playlist"), _("M3U file|*.m3u|XSPF playlist|*.xspf"),
                      showPlaylistSaveCB, kSAVE );
 }
 
index 814de0e..832e77c 100644 (file)
@@ -908,7 +908,9 @@ void Playlist::OnSave( wxCommandEvent& WXUNUSED(event) )
         char *psz_desc;
         char *psz_filter;
         char *psz_module;
-    } formats[] = {{ _("M3U file"), "*.m3u", "export-m3u" }};
+    } formats[] = {{ _("M3U file"), "*.m3u", "export-m3u" },
+                   { _("XSPF playlist"), "*.xspf", "export-xspf"}
+    };
 
     wxString filter = wxT("");
 
@@ -943,7 +945,7 @@ void Playlist::OnSave( wxCommandEvent& WXUNUSED(event) )
 void Playlist::OnOpen( wxCommandEvent& WXUNUSED(event) )
 {
     wxFileDialog dialog( this, wxU(_("Open playlist")), wxT(""), wxT(""),
-        wxT("All playlists|*.pls;*.m3u;*.asx;*.b4s|M3U files|*.m3u"), wxOPEN );
+        wxT("All playlists|*.pls;*.m3u;*.asx;*.b4s;*.xspf|XSPF playlist|*.xspf|M3U files|*.m3u"), wxOPEN );
 
     if( dialog.ShowModal() == wxID_OK )
     {
index f650bd0..ec0a1a4 100644 (file)
@@ -1,3 +1,7 @@
-SOURCES_export = export.c \
-                m3u.c    \
-                old.c
+SOURCES_export = \
+       export.c \
+       m3u.c \
+       xspf.c \
+       xspf.h \
+       old.c \
+       $(NULL)
index 320743c..af49869 100644 (file)
@@ -31,7 +31,7 @@
  ***************************************************************************/
 int Export_M3U    ( vlc_object_t *p_intf );
 int Export_Old    ( vlc_object_t *p_intf );
-
+int E_(xspf_export_playlist)( vlc_object_t *p_intf );
 
 /*****************************************************************************
  * Module descriptor
@@ -52,4 +52,10 @@ vlc_module_begin();
         set_capability( "playlist export" , 0);
         set_callbacks( Export_Old , NULL );
 
+    add_submodule();
+        set_description( _("XSPF playlist export") );
+        add_shortcut( "export-xspf" );
+        set_capability( "playlist export" , 0);
+        set_callbacks( E_(xspf_export_playlist) , NULL );
+
 vlc_module_end();
diff --git a/modules/misc/playlist/xspf.c b/modules/misc/playlist/xspf.c
new file mode 100644 (file)
index 0000000..59ab4b9
--- /dev/null
@@ -0,0 +1,295 @@
+/******************************************************************************
+ * Copyright (C) 2006 Daniel Stränger <vlc at schmaller dot de>
+ *
+ * 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.
+ *******************************************************************************/
+
+/**
+ * \file modules/misc/playlist/xspf.c
+ * \brief XSPF playlist export functions
+ */
+#include <stdio.h>
+#include <vlc/vlc.h>
+#include <vlc/intf.h>
+#include "vlc_meta.h"
+#include "vlc_strings.h"
+#include "xspf.h"
+
+/**
+ * \brief Prints the XSPF header to file, writes each item by xspf_export_item()
+ * and closes the open xml elements
+ * \param p_this the VLC playlist object
+ * \return VLC_SUCCESS if some memory is available, otherwise VLC_ENONMEM
+ */
+int E_(xspf_export_playlist)( vlc_object_t *p_this )
+{
+    const playlist_t *p_playlist = (playlist_t *)p_this;
+    const playlist_export_t *p_export =
+        (playlist_export_t *)p_playlist->p_private;
+    int              i;
+    char             *psz;
+    char             *psz_temp;
+    playlist_item_t **pp_items = NULL;
+    int               i_size;
+    playlist_item_t  *p_node;
+
+    /* write XSPF XML header - since we don't use <extension>,
+     * we get by with version 0 */
+    fprintf( p_export->p_file, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" );
+    fprintf( p_export->p_file,
+             "<playlist version=\"0\" xmlns=\"http://xspf.org/ns/0/\">\n" );
+
+    /* save tho whole playlist or only the current node */
+#define p_item p_playlist->status.p_item
+    if ( p_item )
+    {
+        for (i = 0; i < p_item->i_parents; i++ )
+        {
+            if ( p_item->pp_parents[i]->p_parent->input.i_type
+                 == ITEM_TYPE_PLAYLIST )
+            {
+                /* set the current node and its children */
+                p_node   = p_item->pp_parents[i]->p_parent;
+                pp_items = p_node->pp_children;
+                i_size   = p_node->i_children;
+#undef p_item
+
+                /* save name of the playlist node */
+                psz_temp = convert_xml_special_chars( p_node->input.psz_name );
+                if ( *psz_temp )
+                    fprintf(  p_export->p_file, "\t<title>%s</title>\n",
+                              psz_temp );
+                free( psz_temp );
+
+                /* save the creator of the playlist node */
+                psz = vlc_input_item_GetInfo( &p_node->input,
+                                              _(VLC_META_INFO_CAT),
+                                              _(VLC_META_ARTIST) );
+                if ( psz && !*psz )
+                {
+                    free ( psz );
+                    psz = NULL;
+                }
+
+                if ( !psz )
+                    psz = vlc_input_item_GetInfo( &p_node->input,
+                                                  _(VLC_META_INFO_CAT),
+                                                  _(VLC_META_AUTHOR) );
+
+                psz_temp = convert_xml_special_chars( psz );
+
+                if ( psz ) free( psz );
+                if ( *psz_temp )
+                    fprintf(  p_export->p_file, "\t<creator>%s</creator>\n",
+                              psz_temp );
+                free( psz_temp );
+
+                /* save location of the playlist node */
+                psz = assertUTF8URI( p_export->psz_filename );
+                if ( psz && *psz )
+                {
+                    fprintf( p_export->p_file, "\t<location>%s</location>\n",
+                             psz );
+                    free( psz );
+                }
+                break;
+            }
+        }
+    }
+
+    /* prepare all the playlist children for export */
+    if ( !pp_items )
+    {
+        pp_items = p_playlist->pp_items;
+        i_size   = p_playlist->i_size;
+    }
+
+    /* export all items */
+    fprintf( p_export->p_file, "\t<trackList>\n" );
+    for ( i = 0; i < i_size; i++ )
+    {
+        xspf_export_item( pp_items[i], p_export->p_file );
+    }
+
+    /* close the header elements */
+    fprintf( p_export->p_file, "\t</trackList>\n" );
+    fprintf( p_export->p_file, "</playlist>\n" );
+
+    return VLC_SUCCESS;
+}
+
+/**
+ * \brief exports one item to file or traverse if item is a node
+ * \param p_item playlist item to export
+ * \param p_file file to write xml-converted item to
+ */
+static void xspf_export_item( playlist_item_t *p_item, FILE *p_file )
+{
+    int i;       /**< iterator for all children if the current item is a node */
+    char *psz;
+    char *psz_temp;
+
+    if ( !p_item )
+        return;
+
+    /** \todo only "flat" playlists supported at this time.
+     *  extend to save the tree structure.
+     */
+    /* if we get a node here, we must traverse it */
+    if ( p_item->i_children > 0 )
+    {
+        for ( i = 0; i < p_item->i_children; i++ )
+        {
+            xspf_export_item( p_item->pp_children[i], p_file );
+        }
+        return;
+    }
+
+    /* leaves can be written directly */
+    fprintf( p_file, "\t\t<track>\n" );
+
+    /* -> the location */
+    if ( p_item->input.psz_uri && *p_item->input.psz_uri )
+    {
+        psz = assertUTF8URI( p_item->input.psz_uri );
+        fprintf( p_file, "\t\t\t<location>%s</location>\n", psz );
+        free( psz );
+    }
+
+    /* -> the name/title (only if different from uri)*/
+    if ( p_item->input.psz_name &&
+         p_item->input.psz_uri &&
+         strcmp( p_item->input.psz_uri, p_item->input.psz_name ) )
+    {
+        psz_temp = convert_xml_special_chars( p_item->input.psz_name );
+        if ( *psz_temp )
+            fprintf( p_file, "\t\t\t<title>%s</title>\n", psz_temp );
+        free( psz_temp );
+    }
+
+    /* -> the artist/creator */
+    psz = vlc_input_item_GetInfo( &p_item->input,
+                                  _(VLC_META_INFO_CAT),
+                                  _(VLC_META_ARTIST) );
+    if ( psz && !*psz )
+    {
+        free ( psz );
+        psz = NULL;
+    }
+    if ( !psz )
+        psz = vlc_input_item_GetInfo( &p_item->input,
+                                      _(VLC_META_INFO_CAT),
+                                      _(VLC_META_AUTHOR) );
+    psz_temp = convert_xml_special_chars( psz );
+    if ( psz ) free( psz );
+    if ( *psz_temp )
+        fprintf( p_file, "\t\t\t<creator>%s</creator>\n", psz_temp );
+    free( psz_temp );
+
+    /* -> the album */
+    psz = vlc_input_item_GetInfo( &p_item->input,
+                                  _(VLC_META_INFO_CAT),
+                                  _(VLC_META_COLLECTION) );
+    psz_temp = convert_xml_special_chars( psz );
+    if ( psz ) free( psz );
+    if ( *psz_temp )
+        fprintf( p_file, "\t\t\t<album>%s</album>\n", psz_temp );
+    free( psz_temp );
+
+    /* -> the track number */
+    psz = vlc_input_item_GetInfo( &p_item->input,
+                                  _(VLC_META_INFO_CAT),
+                                  _(VLC_META_SEQ_NUM) );
+    if ( psz )
+    {
+        if ( *psz )
+            fprintf( p_file, "\t\t\t<trackNum>%i</trackNum>\n", atoi( psz ) );
+        free( psz );
+    }
+
+    /* -> the duration */
+    if ( p_item->input.i_duration > 0 )
+    {
+        fprintf( p_file, "\t\t\t<duration>%ld</duration>\n",
+                 (long)(p_item->input.i_duration / 1000) );
+    }
+
+    fprintf( p_file, "\t\t</track>\n" );
+
+    return;
+}
+
+/**
+ * \param psz_name the location of the media ressource (e.g. local file,
+ *        device, network stream, etc.)
+ * \return a new char buffer which asserts that the location is valid UTF-8
+ *         and a valid URI
+ * \note the returned buffer must be freed, when it isn't used anymore
+ */
+static char *assertUTF8URI( char *psz_name )
+{
+    char *psz_ret = NULL;              /**< the new result buffer to return */
+    char *psz_s = NULL, *psz_d = NULL; /**< src & dest pointers for URI conversion */
+    vlc_bool_t b_name_is_uri = VLC_FALSE;
+
+    if ( !psz_name || !*psz_name )
+        return NULL;
+
+    /* check that string is valid UTF-8 */
+    /* XXX: Why do we even need to do that ? (all strings in core are UTF-8 encoded */
+    if( !( psz_s = EnsureUTF8( psz_name ) ) )
+        return NULL;
+
+    /* max. 3x for URI conversion (percent escaping) and
+       8 bytes for "file://" and NULL-termination */
+    psz_ret = (char *)malloc( sizeof(char)*strlen(psz_name)*6*3+8 );
+    if ( !psz_ret )
+        return NULL;
+
+    /** \todo check for a valid scheme part preceding the colon */
+    if ( strchr( psz_s, ':' ) )
+    {
+        psz_d = psz_ret;
+        b_name_is_uri = VLC_TRUE;
+    }
+    /* assume "file" scheme if no scheme-part is included */
+    else
+    {
+        strcpy( psz_ret, "file://" );
+        psz_d = psz_ret + 7;
+    }
+
+    while ( *psz_s )
+    {
+        /* percent-encode all non-ASCII and the XML special characters and the percent sign itself */
+        if ( *psz_s & B10000000 ||
+             *psz_s == '<' ||
+             *psz_s == '>' ||
+             *psz_s == '&' ||
+             *psz_s == ' ' ||
+             ( *psz_s == '%' && !b_name_is_uri ) )
+        {
+            *psz_d++ = '%';
+            *psz_d++ = hexchars[(*psz_s >> 4) & B00001111];
+            *psz_d++ = hexchars[*psz_s & B00001111];
+        } else
+            *psz_d++ = *psz_s;
+
+        psz_s++;
+    }
+    *psz_d = '\0';
+
+    return (char *)realloc( psz_ret, sizeof(char)*strlen( psz_ret ) + 1 );
+}
diff --git a/modules/misc/playlist/xspf.h b/modules/misc/playlist/xspf.h
new file mode 100644 (file)
index 0000000..e277373
--- /dev/null
@@ -0,0 +1,37 @@
+/*****************************************************************************
+ * Copyright (C) 2006 Daniel Stränger <vlc at schmaller dot de>
+ *
+ * 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.
+ *******************************************************************************/
+/**
+ * \file modules/misc/playlist/xspf.h
+ * \brief XSPF playlist export module header file
+ */
+
+/* defs */
+#define B10000000 0x80
+#define B01000000 0x40
+#define B11000000 0xc0
+#define B00001111 0x0f
+
+#define XSPF_MAX_CONTENT 2000
+
+/* constants */
+const char hexchars[16] = "0123456789ABCDEF";
+
+/* prototypes */
+int E_(xspf_export_playlist)( vlc_object_t * );
+static void xspf_export_item( playlist_item_t *, FILE * );
+static char *assertUTF8URI( char * );
index de045bf..9b81e44 100644 (file)
@@ -5,8 +5,8 @@
  * $Id$
  *
  * Authors: Rémi Denis-Courmont <rem # videolan.org> (original plugin)
- *          Christian Henz <henz # c-lab.de> 
- * 
+ *          Christian Henz <henz # c-lab.de>
+ *
  * UPnP Plugin using the Intel SDK (libupnp) instead of CyberLink
  *
  * This program is free software; you can redistribute it and/or modify
@@ -41,6 +41,8 @@
 #include <vlc/vlc.h>
 #include <vlc/intf.h>
 
+#include "vlc_strings.h"
+
 
 // VLC handle
 
@@ -57,7 +59,7 @@ const char* MEDIA_SERVER_DEVICE_TYPE = "urn:schemas-upnp-org:device:MediaServer:
 const char* CONTENT_DIRECTORY_SERVICE_TYPE = "urn:schemas-upnp-org:service:ContentDirectory:1";
 
 
-// Classes 
+// Classes
 
 class MediaServer;
 class MediaServerList;
@@ -66,51 +68,51 @@ class Container;
 
 // Cookie that is passed to the callback
 
-typedef struct 
+typedef struct
 {
     services_discovery_t* serviceDiscovery;
     UpnpClient_Handle clientHandle;
-    MediaServerList* serverList; 
+    MediaServerList* serverList;
 } Cookie;
 
 
 // Class definitions...
 
-class Lockable 
-{  
+class Lockable
+{
 public:
 
-    Lockable( Cookie* c ) 
+    Lockable( Cookie* c )
     {
-       vlc_mutex_init( c->serviceDiscovery, &_mutex );
+    vlc_mutex_init( c->serviceDiscovery, &_mutex );
     }
 
-    ~Lockable() 
+    ~Lockable()
     {
-       vlc_mutex_destroy( &_mutex );
+    vlc_mutex_destroy( &_mutex );
     }
 
     void lock() { vlc_mutex_lock( &_mutex ); }
     void unlock() { vlc_mutex_unlock( &_mutex ); }
 
 private:
-    
+
     vlc_mutex_t _mutex;
 };
 
 
-class Locker 
+class Locker
 {
 public:
-    Locker( Lockable* l ) 
+    Locker( Lockable* l )
     {
-       _lockable = l;
-       _lockable->lock();
+    _lockable = l;
+    _lockable->lock();
     }
 
-    ~Locker() 
+    ~Locker()
     {
-       _lockable->unlock();
+    _lockable->unlock();
     }
 
 private:
@@ -123,10 +125,10 @@ class MediaServer
 public:
 
     static void parseDeviceDescription( IXML_Document* doc, const char* location, Cookie* cookie );
-    
+
     MediaServer( const char* UDN, const char* friendlyName, Cookie* cookie );
     ~MediaServer();
-    
+
     const char* getUDN() const;
     const char* getFriendlyName() const;
 
@@ -150,13 +152,13 @@ private:
     IXML_Document* _browseAction( const char*, const char*, const char*, const char*, const char*, const char* );
 
     Cookie* _cookie;
-  
+
     Container* _contents;
     playlist_item_t* _playlistNode;
 
     std::string _UDN;
     std::string _friendlyName;
-  
+
     std::string _contentDirectoryEventURL;
     std::string _contentDirectoryControlURL;
 
@@ -186,7 +188,7 @@ private:
 };
 
 
-class Item 
+class Item
 {
 public:
 
@@ -202,7 +204,7 @@ public:
 private:
 
     playlist_item_t* _playlistNode;
+
     Container* _parent;
     std::string _objectID;
     std::string _title;
@@ -210,7 +212,7 @@ private:
 };
 
 
-class Container 
+class Container
 {
 public:
 
@@ -268,7 +270,6 @@ vlc_module_end();
 static Lockable* CallbackLock;
 static int Callback( Upnp_EventType eventType, void* event, void* pCookie );
 
-char* xml_makeSpecialChars( const char* in );
 const char* xml_getChildElementValue( IXML_Element* parent, const char* tagName );
 IXML_Document* parseBrowseResult( IXML_Document* doc );
 
@@ -279,8 +280,8 @@ static int Open( vlc_object_t *p_this )
 {
     services_discovery_t *p_sd = ( services_discovery_t* )p_this;
     services_discovery_sys_t *p_sys  = ( services_discovery_sys_t * )
-       malloc( sizeof( services_discovery_sys_t ) );
-    
+    malloc( sizeof( services_discovery_sys_t ) );
+
     playlist_view_t *p_view;
     vlc_value_t val;
 
@@ -289,12 +290,12 @@ static int Open( vlc_object_t *p_this )
 
     /* Create our playlist node */
     p_sys->p_playlist = ( playlist_t * )vlc_object_find( p_sd,
-                                                        VLC_OBJECT_PLAYLIST,
-                                                        FIND_ANYWHERE );
+                             VLC_OBJECT_PLAYLIST,
+                             FIND_ANYWHERE );
     if( !p_sys->p_playlist )
     {
-       msg_Warn( p_sd, "unable to find playlist, cancelling UPnP listening" );
-       return VLC_EGENERIC;
+    msg_Warn( p_sd, "unable to find playlist, cancelling UPnP listening" );
+    return VLC_EGENERIC;
     }
 
     p_view = playlist_ViewFind( p_sys->p_playlist, VIEW_CATEGORY );
@@ -315,9 +316,9 @@ static void Close( vlc_object_t *p_this )
 
     if( p_sys->p_playlist )
     {
-       playlist_NodeDelete( p_sys->p_playlist, p_sys->p_node, VLC_TRUE,
-                            VLC_TRUE );
-       vlc_object_release( p_sys->p_playlist );
+    playlist_NodeDelete( p_sys->p_playlist, p_sys->p_node, VLC_TRUE,
+                 VLC_TRUE );
+    vlc_object_release( p_sys->p_playlist );
     }
 
     free( p_sys );
@@ -326,12 +327,12 @@ static void Close( vlc_object_t *p_this )
 static void Run( services_discovery_t* p_sd )
 {
     int res;
-  
+
     res = UpnpInit( 0, 0 );
-    if( res != UPNP_E_SUCCESS ) 
+    if( res != UPNP_E_SUCCESS )
     {
-       msg_Err( p_sd, "%s", UpnpGetErrorMessage( res ) );
-       return;
+    msg_Err( p_sd, "%s", UpnpGetErrorMessage( res ) );
+    return;
     }
 
     Cookie cookie;
@@ -341,23 +342,23 @@ static void Run( services_discovery_t* p_sd )
     CallbackLock = new Lockable( &cookie );
 
     res = UpnpRegisterClient( Callback, &cookie, &cookie.clientHandle );
-    if( res != UPNP_E_SUCCESS ) 
+    if( res != UPNP_E_SUCCESS )
     {
-       msg_Err( p_sd, "%s", UpnpGetErrorMessage( res ) );
-       goto shutDown;
+    msg_Err( p_sd, "%s", UpnpGetErrorMessage( res ) );
+    goto shutDown;
     }
 
     res = UpnpSearchAsync( cookie.clientHandle, 5, MEDIA_SERVER_DEVICE_TYPE, &cookie );
-    if( res != UPNP_E_SUCCESS ) 
+    if( res != UPNP_E_SUCCESS )
     {
-       msg_Err( p_sd, "%s", UpnpGetErrorMessage( res ) );
-       goto shutDown;
+    msg_Err( p_sd, "%s", UpnpGetErrorMessage( res ) );
+    goto shutDown;
     }
+
     msg_Dbg( p_sd, "UPnP discovery started" );
-    while( !p_sd->b_die ) 
+    while( !p_sd->b_die )
     {
-       msleep( 500 );
+    msleep( 500 );
     }
 
     msg_Dbg( p_sd, "UPnP discovery stopped" );
@@ -372,7 +373,7 @@ static void Run( services_discovery_t* p_sd )
 // XML utility functions:
 
 // Returns the value of a child element, or 0 on error
-const char* xml_getChildElementValue( IXML_Element* parent, const char* tagName ) 
+const char* xml_getChildElementValue( IXML_Element* parent, const char* tagName )
 {
     if ( !parent ) return 0;
     if ( !tagName ) return 0;
@@ -392,72 +393,14 @@ const char* xml_getChildElementValue( IXML_Element* parent, const char* tagName
     return ixmlNode_getNodeValue( textNode );
 }
 
-
-// Replaces "&lt;" with "<" etc.
-// Returns a newly created string that has to be freed by the caller.
-// Returns 0 on error ( out of mem )
-// \TODO: Probably not very robust!!!
-char* xml_makeSpecialChars( const char* in ) 
-{
-    if ( !in ) return 0;
-
-    char* result = ( char* )malloc( strlen( in ) + 1 );
-    if ( !result ) return 0;
-
-    char* out = result;
-
-    while( *in ) 
-    {
-       if ( strncmp( "&amp;", in, 5 ) == 0 ) 
-       {
-           *out = '&';
-
-           in += 5;
-           out++;
-       } 
-       else if ( strncmp( "&quot;", in, 6 ) == 0 ) 
-       {
-           *out = '"';
-
-           in += 6;
-           out++;
-       } 
-       else if ( strncmp( "&gt;", in, 4 ) == 0 ) 
-       {
-           *out = '>';
-           
-           in += 4;
-           out++;
-       } 
-       else if ( strncmp( "&lt;", in, 4 ) == 0 ) 
-       {
-           *out = '<';
-
-           in += 4;
-           out++;
-       } 
-       else 
-       {
-           *out = *in;
-
-           in++;
-           out++;
-       }
-    }
-
-    *out = '\0';
-    return result;
-}
-
-
 // Extracts the result document from a SOAP response
-IXML_Document* parseBrowseResult( IXML_Document* doc ) 
+IXML_Document* parseBrowseResult( IXML_Document* doc )
 {
     if ( !doc ) return 0;
-  
+
     IXML_NodeList* resultList = ixmlDocument_getElementsByTagName( doc, "Result" );
     if ( !resultList ) return 0;
-  
+
     IXML_Node* resultNode = ixmlNodeList_item( resultList, 0 );
 
     ixmlNodeList_free( resultList );
@@ -468,7 +411,7 @@ IXML_Document* parseBrowseResult( IXML_Document* doc )
     if ( !textNode ) return 0;
 
     const char* resultString = ixmlNode_getNodeValue( textNode );
-    char* resultXML = xml_makeSpecialChars( resultString );
+    char* resultXML = convert_xml_special_chars( resultString );
 
     IXML_Document* browseDoc = ixmlParseBuffer( resultXML );
 
@@ -479,67 +422,67 @@ IXML_Document* parseBrowseResult( IXML_Document* doc )
 
 
 // Handles all UPnP events
-static int Callback( Upnp_EventType eventType, void* event, void* pCookie ) 
+static int Callback( Upnp_EventType eventType, void* event, void* pCookie )
 {
     Locker locker( CallbackLock );
 
     Cookie* cookie = ( Cookie* )pCookie;
 
     switch( eventType ) {
-    
+
     case UPNP_DISCOVERY_ADVERTISEMENT_ALIVE:
     case UPNP_DISCOVERY_SEARCH_RESULT:
-       {
-           struct Upnp_Discovery* discovery = ( struct Upnp_Discovery* )event;
-  
-           IXML_Document *descriptionDoc = 0;
-      
-           int res;
-           res = UpnpDownloadXmlDoc( discovery->Location, &descriptionDoc );
-           if ( res != UPNP_E_SUCCESS ) 
-           {
-             msg_Dbg( cookie->serviceDiscovery, "%s:%d: Could not download device description!", __FILE__, __LINE__ );
-             return res;
-           }
-
-           MediaServer::parseDeviceDescription( descriptionDoc, discovery->Location, cookie );
-
-           ixmlDocument_free( descriptionDoc );
-       }
-       break;
-    
+    {
+        struct Upnp_Discovery* discovery = ( struct Upnp_Discovery* )event;
+
+        IXML_Document *descriptionDoc = 0;
+
+        int res;
+        res = UpnpDownloadXmlDoc( discovery->Location, &descriptionDoc );
+        if ( res != UPNP_E_SUCCESS )
+        {
+          msg_Dbg( cookie->serviceDiscovery, "%s:%d: Could not download device description!", __FILE__, __LINE__ );
+          return res;
+        }
+
+        MediaServer::parseDeviceDescription( descriptionDoc, discovery->Location, cookie );
+
+        ixmlDocument_free( descriptionDoc );
+    }
+    break;
+
     case UPNP_DISCOVERY_ADVERTISEMENT_BYEBYE:
-       {
-           struct Upnp_Discovery* discovery = ( struct Upnp_Discovery* )event;
+    {
+        struct Upnp_Discovery* discovery = ( struct Upnp_Discovery* )event;
 
-           cookie->serverList->removeServer( discovery->DeviceId );
-       }
-       break;
+        cookie->serverList->removeServer( discovery->DeviceId );
+    }
+    break;
 
     case UPNP_EVENT_RECEIVED:
-       {
-           Upnp_Event* e = ( Upnp_Event* )event;
+    {
+        Upnp_Event* e = ( Upnp_Event* )event;
 
-           MediaServer* server = cookie->serverList->getServerBySID( e->Sid );
-           if ( server ) server->fetchContents(); 
-       }
-       break;
+        MediaServer* server = cookie->serverList->getServerBySID( e->Sid );
+        if ( server ) server->fetchContents();
+    }
+    break;
 
     case UPNP_EVENT_AUTORENEWAL_FAILED:
     case UPNP_EVENT_SUBSCRIPTION_EXPIRED:
-       {
-           // Re-subscribe...
+    {
+        // Re-subscribe...
 
-           Upnp_Event_Subscribe* s = ( Upnp_Event_Subscribe* )event;
+        Upnp_Event_Subscribe* s = ( Upnp_Event_Subscribe* )event;
+
+        MediaServer* server = cookie->serverList->getServerBySID( s->Sid );
+        if ( server ) server->subscribeToContentDirectory();
+    }
+    break;
 
-           MediaServer* server = cookie->serverList->getServerBySID( s->Sid );
-           if ( server ) server->subscribeToContentDirectory();
-       }
-       break;
-       
     default:
-       msg_Dbg( cookie->serviceDiscovery, "%s:%d: DEBUG: UNHANDLED EVENT ( TYPE=%d )", __FILE__, __LINE__, eventType );
-       break;
+    msg_Dbg( cookie->serviceDiscovery, "%s:%d: DEBUG: UNHANDLED EVENT ( TYPE=%d )", __FILE__, __LINE__, eventType );
+    break;
     }
 
     return UPNP_E_SUCCESS;
@@ -550,127 +493,127 @@ static int Callback( Upnp_EventType eventType, void* event, void* pCookie )
 
 // MediaServer...
 
-void MediaServer::parseDeviceDescription( IXML_Document* doc, const char* location, Cookie* cookie ) 
+void MediaServer::parseDeviceDescription( IXML_Document* doc, const char* location, Cookie* cookie )
 {
     if ( !doc ) { msg_Dbg( cookie->serviceDiscovery, "%s:%d: NULL", __FILE__, __LINE__ ); return; }
     if ( !location ) { msg_Dbg( cookie->serviceDiscovery, "%s:%d: NULL", __FILE__, __LINE__ ); return; }
-  
+
     const char* baseURL = location;
 
     // Try to extract baseURL
 
     IXML_NodeList* urlList = ixmlDocument_getElementsByTagName( doc, "baseURL" );
-    if ( urlList ) 
-    {  
-       if ( IXML_Node* urlNode = ixmlNodeList_item( urlList, 0 ) ) 
-       {
-           IXML_Node* textNode = ixmlNode_getFirstChild( urlNode );
-           if ( textNode ) baseURL = ixmlNode_getNodeValue( textNode );
-       }
-      
-       ixmlNodeList_free( urlList );
+    if ( urlList )
+    {
+    if ( IXML_Node* urlNode = ixmlNodeList_item( urlList, 0 ) )
+    {
+        IXML_Node* textNode = ixmlNode_getFirstChild( urlNode );
+        if ( textNode ) baseURL = ixmlNode_getNodeValue( textNode );
+    }
+
+    ixmlNodeList_free( urlList );
     }
-  
+
     // Get devices
 
     IXML_NodeList* deviceList = ixmlDocument_getElementsByTagName( doc, "device" );
     if ( deviceList )
     {
-  
-       for ( unsigned int i = 0; i < ixmlNodeList_length( deviceList ); i++ ) 
-       {
-           IXML_Element* deviceElement = ( IXML_Element* )ixmlNodeList_item( deviceList, i );
-    
-           const char* deviceType = xml_getChildElementValue( deviceElement, "deviceType" );
-           if ( !deviceType ) { msg_Dbg( cookie->serviceDiscovery, "%s:%d: no deviceType!", __FILE__, __LINE__ ); continue; }
-           if ( strcmp( MEDIA_SERVER_DEVICE_TYPE, deviceType ) != 0 ) continue;
-   
-           const char* UDN = xml_getChildElementValue( deviceElement, "UDN" );
-           if ( !UDN ) { msg_Dbg( cookie->serviceDiscovery, "%s:%d: no UDN!", __FILE__, __LINE__ ); continue; }    
-           if ( cookie->serverList->getServer( UDN ) != 0 ) continue;
-    
-           const char* friendlyName = xml_getChildElementValue( deviceElement, "friendlyName" );
-           if ( !friendlyName ) { msg_Dbg( cookie->serviceDiscovery, "%s:%d: no friendlyName!", __FILE__, __LINE__ ); continue; }
-    
-           MediaServer* server = new MediaServer( UDN, friendlyName, cookie );
-           if ( !cookie->serverList->addServer( server ) ) {
-
-               delete server;
-               server = 0;
-               continue;
-           }
-           
-           // Check for ContentDirectory service...
-           
-           IXML_NodeList* serviceList = ixmlElement_getElementsByTagName( deviceElement, "service" );
-           if ( serviceList ) 
-           {
-               for ( unsigned int j = 0; j < ixmlNodeList_length( serviceList ); j++ ) 
-               {
-                   IXML_Element* serviceElement = ( IXML_Element* )ixmlNodeList_item( serviceList, j );
-               
-                   const char* serviceType = xml_getChildElementValue( serviceElement, "serviceType" );
-                   if ( !serviceType ) continue;
-                   if ( strcmp( CONTENT_DIRECTORY_SERVICE_TYPE, serviceType ) != 0 ) continue;
-                   
-                   const char* eventSubURL = xml_getChildElementValue( serviceElement, "eventSubURL" );
-                   if ( !eventSubURL ) continue;
-               
-                   const char* controlURL = xml_getChildElementValue( serviceElement, "controlURL" );
-                   if ( !controlURL ) continue;
-
-                   // Try to subscribe to ContentDirectory service
-      
-                   char* url = ( char* )malloc( strlen( baseURL ) + strlen( eventSubURL ) + 1 );
-                   if ( url ) 
-                   {
-                           char* s1 = strdup( baseURL );
-                           char* s2 = strdup( eventSubURL );
-                   
-                           if ( UpnpResolveURL( s1, s2, url ) == UPNP_E_SUCCESS ) 
-                           {
-                               // msg_Dbg( cookie->serviceDiscovery, "CDS EVENT URL: %s", url );
-                               
-                               server->setContentDirectoryEventURL( url );
-                               server->subscribeToContentDirectory();
-                           }
-
-                           free( s1 );
-                           free( s2 );
-                           free( url );
-                   }
-                   
-                   // Try to browse content directory...
-                   
-                   url = ( char* )malloc( strlen( baseURL ) + strlen( controlURL ) + 1 );
-                   if ( url ) 
-                   {
-                       char* s1 = strdup( baseURL );
-                       char* s2 = strdup( controlURL );
-                   
-                       if ( UpnpResolveURL( s1, s2, url ) == UPNP_E_SUCCESS ) 
-                       {
-                           // msg_Dbg( cookie->serviceDiscovery, "CDS CTRL URL: %s", url );
-                           
-                           server->setContentDirectoryControlURL( url );
-                           server->fetchContents();
-                       }
-       
-                       free( s1 );
-                       free( s2 );
-                       free( url );
-                   }
-               }
-                               
-               ixmlNodeList_free( serviceList );
-           }
-       }
-
-       ixmlNodeList_free( deviceList );
+
+    for ( unsigned int i = 0; i < ixmlNodeList_length( deviceList ); i++ )
+    {
+        IXML_Element* deviceElement = ( IXML_Element* )ixmlNodeList_item( deviceList, i );
+
+        const char* deviceType = xml_getChildElementValue( deviceElement, "deviceType" );
+        if ( !deviceType ) { msg_Dbg( cookie->serviceDiscovery, "%s:%d: no deviceType!", __FILE__, __LINE__ ); continue; }
+        if ( strcmp( MEDIA_SERVER_DEVICE_TYPE, deviceType ) != 0 ) continue;
+
+        const char* UDN = xml_getChildElementValue( deviceElement, "UDN" );
+        if ( !UDN ) { msg_Dbg( cookie->serviceDiscovery, "%s:%d: no UDN!", __FILE__, __LINE__ ); continue; }
+        if ( cookie->serverList->getServer( UDN ) != 0 ) continue;
+
+        const char* friendlyName = xml_getChildElementValue( deviceElement, "friendlyName" );
+        if ( !friendlyName ) { msg_Dbg( cookie->serviceDiscovery, "%s:%d: no friendlyName!", __FILE__, __LINE__ ); continue; }
+
+        MediaServer* server = new MediaServer( UDN, friendlyName, cookie );
+        if ( !cookie->serverList->addServer( server ) ) {
+
+        delete server;
+        server = 0;
+        continue;
+        }
+
+        // Check for ContentDirectory service...
+
+        IXML_NodeList* serviceList = ixmlElement_getElementsByTagName( deviceElement, "service" );
+        if ( serviceList )
+        {
+            for ( unsigned int j = 0; j < ixmlNodeList_length( serviceList ); j++ )
+        {
+            IXML_Element* serviceElement = ( IXML_Element* )ixmlNodeList_item( serviceList, j );
+
+            const char* serviceType = xml_getChildElementValue( serviceElement, "serviceType" );
+            if ( !serviceType ) continue;
+            if ( strcmp( CONTENT_DIRECTORY_SERVICE_TYPE, serviceType ) != 0 ) continue;
+
+            const char* eventSubURL = xml_getChildElementValue( serviceElement, "eventSubURL" );
+            if ( !eventSubURL ) continue;
+
+            const char* controlURL = xml_getChildElementValue( serviceElement, "controlURL" );
+            if ( !controlURL ) continue;
+
+            // Try to subscribe to ContentDirectory service
+
+            char* url = ( char* )malloc( strlen( baseURL ) + strlen( eventSubURL ) + 1 );
+            if ( url )
+            {
+                char* s1 = strdup( baseURL );
+                char* s2 = strdup( eventSubURL );
+
+                if ( UpnpResolveURL( s1, s2, url ) == UPNP_E_SUCCESS )
+                {
+                // msg_Dbg( cookie->serviceDiscovery, "CDS EVENT URL: %s", url );
+
+                server->setContentDirectoryEventURL( url );
+                server->subscribeToContentDirectory();
+                }
+
+                free( s1 );
+                free( s2 );
+                free( url );
+            }
+
+            // Try to browse content directory...
+
+            url = ( char* )malloc( strlen( baseURL ) + strlen( controlURL ) + 1 );
+            if ( url )
+            {
+            char* s1 = strdup( baseURL );
+            char* s2 = strdup( controlURL );
+
+            if ( UpnpResolveURL( s1, s2, url ) == UPNP_E_SUCCESS )
+            {
+                // msg_Dbg( cookie->serviceDiscovery, "CDS CTRL URL: %s", url );
+
+                server->setContentDirectoryControlURL( url );
+                server->fetchContents();
+            }
+
+            free( s1 );
+            free( s2 );
+            free( url );
+            }
+        }
+
+        ixmlNodeList_free( serviceList );
+        }
+    }
+
+    ixmlNodeList_free( deviceList );
     }
 }
 
-MediaServer::MediaServer( const char* UDN, const char* friendlyName, Cookie* cookie ) 
+MediaServer::MediaServer( const char* UDN, const char* friendlyName, Cookie* cookie )
 {
     _cookie = cookie;
 
@@ -681,14 +624,14 @@ MediaServer::MediaServer( const char* UDN, const char* friendlyName, Cookie* coo
     _playlistNode = 0;
 }
 
-MediaServer::~MediaServer() 
-{ 
-    if ( _contents ) 
+MediaServer::~MediaServer()
+{
+    if ( _contents )
     {
-       playlist_NodeDelete( _cookie->serviceDiscovery->p_sys->p_playlist, 
-                           _playlistNode,
-                           true,
-                           true );
+    playlist_NodeDelete( _cookie->serviceDiscovery->p_sys->p_playlist,
+                _playlistNode,
+                true,
+                true );
     }
 
     delete _contents;
@@ -706,7 +649,7 @@ const char* MediaServer::getFriendlyName() const
     return s;
 }
 
-void MediaServer::setContentDirectoryEventURL( const char* url ) 
+void MediaServer::setContentDirectoryEventURL( const char* url )
 {
     _contentDirectoryEventURL = url;
 }
@@ -717,7 +660,7 @@ const char* MediaServer::getContentDirectoryEventURL() const
     return s;
 }
 
-void MediaServer::setContentDirectoryControlURL( const char* url ) 
+void MediaServer::setContentDirectoryControlURL( const char* url )
 {
     _contentDirectoryControlURL = url;
 }
@@ -727,33 +670,33 @@ const char* MediaServer::getContentDirectoryControlURL() const
     return _contentDirectoryControlURL.c_str();
 }
 
-void MediaServer::subscribeToContentDirectory() 
+void MediaServer::subscribeToContentDirectory()
 {
     const char* url = getContentDirectoryEventURL();
-    if ( !url || strcmp( url, "" ) == 0 ) 
-    { 
-       msg_Dbg( _cookie->serviceDiscovery, "No subscription url set!" ); 
-       return;
+    if ( !url || strcmp( url, "" ) == 0 )
+    {
+    msg_Dbg( _cookie->serviceDiscovery, "No subscription url set!" );
+    return;
     }
 
     int timeOut = 1810;
     Upnp_SID sid;
-  
+
     int res = UpnpSubscribe( _cookie->clientHandle, url, &timeOut, sid );
 
-    if ( res == UPNP_E_SUCCESS ) 
+    if ( res == UPNP_E_SUCCESS )
     {
-       _subscriptionTimeOut = timeOut;
-       memcpy( _subscriptionID, sid, sizeof( Upnp_SID ) );
-    } 
-    else 
+    _subscriptionTimeOut = timeOut;
+    memcpy( _subscriptionID, sid, sizeof( Upnp_SID ) );
+    }
+    else
     {
-       msg_Dbg( _cookie->serviceDiscovery, "%s:%d: WARNING: '%s': %s", __FILE__, __LINE__, getFriendlyName(), UpnpGetErrorMessage( res ) );
+    msg_Dbg( _cookie->serviceDiscovery, "%s:%d: WARNING: '%s': %s", __FILE__, __LINE__, getFriendlyName(), UpnpGetErrorMessage( res ) );
     }
 }
 
-IXML_Document* MediaServer::_browseAction( const char* pObjectID, const char* pBrowseFlag, const char* pFilter, 
-                                          const char* pStartingIndex, const char* pRequestedCount, const char* pSortCriteria ) 
+IXML_Document* MediaServer::_browseAction( const char* pObjectID, const char* pBrowseFlag, const char* pFilter,
+                       const char* pStartingIndex, const char* pRequestedCount, const char* pSortCriteria )
 {
     IXML_Document* action = 0;
     IXML_Document* response = 0;
@@ -789,18 +732,18 @@ IXML_Document* MediaServer::_browseAction( const char* pObjectID, const char* pB
 
     res = UpnpAddToAction( &action, "Browse", serviceType, "SortCriteria", SortCriteria );
     if ( res != UPNP_E_SUCCESS ) { /* msg_Dbg( _cookie->serviceDiscovery, "%s:%d: ERROR: %s", __FILE__, __LINE__, UpnpGetErrorMessage( res ) ); */ goto browseActionCleanup; }
-  
-    res = UpnpSendAction( _cookie->clientHandle, 
-                         url,
-                         CONTENT_DIRECTORY_SERVICE_TYPE, 
-                         0, 
-                         action, 
-                         &response );
-    if ( res != UPNP_E_SUCCESS ) 
-    { 
-       msg_Dbg( _cookie->serviceDiscovery, "%s:%d: ERROR: %s", __FILE__, __LINE__, UpnpGetErrorMessage( res ) ); 
-       ixmlDocument_free( response );
-       response = 0;
+
+    res = UpnpSendAction( _cookie->clientHandle,
+              url,
+              CONTENT_DIRECTORY_SERVICE_TYPE,
+              0,
+              action,
+              &response );
+    if ( res != UPNP_E_SUCCESS )
+    {
+    msg_Dbg( _cookie->serviceDiscovery, "%s:%d: ERROR: %s", __FILE__, __LINE__, UpnpGetErrorMessage( res ) );
+    ixmlDocument_free( response );
+    response = 0;
     }
 
  browseActionCleanup:
@@ -818,22 +761,22 @@ IXML_Document* MediaServer::_browseAction( const char* pObjectID, const char* pB
     return response;
 }
 
-void MediaServer::fetchContents() 
+void MediaServer::fetchContents()
 {
     Container* root = new Container( 0, "0", getFriendlyName() );
     _fetchContents( root );
 
-    if ( _contents ) 
+    if ( _contents )
     {
-       vlc_mutex_lock( &_cookie->serviceDiscovery->p_sys->p_playlist->object_lock );
+    vlc_mutex_lock( &_cookie->serviceDiscovery->p_sys->p_playlist->object_lock );
 
-       playlist_NodeEmpty( _cookie->serviceDiscovery->p_sys->p_playlist, 
-                          _playlistNode,
-                          true );
+    playlist_NodeEmpty( _cookie->serviceDiscovery->p_sys->p_playlist,
+               _playlistNode,
+               true );
 
-       vlc_mutex_unlock( &_cookie->serviceDiscovery->p_sys->p_playlist->object_lock );
-    
-       delete _contents;
+    vlc_mutex_unlock( &_cookie->serviceDiscovery->p_sys->p_playlist->object_lock );
+
+    delete _contents;
     }
 
     _contents = root;
@@ -842,7 +785,7 @@ void MediaServer::fetchContents()
     _buildPlaylist( _contents );
 }
 
-bool MediaServer::_fetchContents( Container* parent ) 
+bool MediaServer::_fetchContents( Container* parent )
 {
     if (!parent) { msg_Dbg( _cookie->serviceDiscovery, "%s:%d: parent==NULL", __FILE__, __LINE__ ); return false; }
 
@@ -854,111 +797,111 @@ bool MediaServer::_fetchContents( Container* parent )
     if ( !result ) { msg_Dbg( _cookie->serviceDiscovery, "%s:%d: ERROR!", __FILE__, __LINE__ ); return false; }
 
     IXML_NodeList* containerNodeList = ixmlDocument_getElementsByTagName( result, "container" );
-    if ( containerNodeList ) 
+    if ( containerNodeList )
     {
-       for ( unsigned int i = 0; i < ixmlNodeList_length( containerNodeList ); i++ ) 
-       {
-           IXML_Element* containerElement = ( IXML_Element* )ixmlNodeList_item( containerNodeList, i );
-      
-           const char* objectID = ixmlElement_getAttribute( containerElement, "id" );
-           if ( !objectID ) continue;
-      
-           const char* childCountStr = ixmlElement_getAttribute( containerElement, "childCount" );
-           if ( !childCountStr ) continue;
-           int childCount = atoi( childCountStr );
-      
-           const char* title = xml_getChildElementValue( containerElement, "dc:title" );
-           if ( !title ) continue;
-      
-           const char* resource = xml_getChildElementValue( containerElement, "res" );
-
-           if ( resource && childCount < 1 ) 
-           { 
-               Item* item = new Item( parent, objectID, title, resource );
-               parent->addItem( item );
-           } 
-           else 
-           {
-               Container* container = new Container( parent, objectID, title );
-               parent->addContainer( container );
-
-               if ( childCount > 0 ) _fetchContents( container );
-           }
-       }
-
-       ixmlNodeList_free( containerNodeList );
+    for ( unsigned int i = 0; i < ixmlNodeList_length( containerNodeList ); i++ )
+    {
+              IXML_Element* containerElement = ( IXML_Element* )ixmlNodeList_item( containerNodeList, i );
+
+        const char* objectID = ixmlElement_getAttribute( containerElement, "id" );
+        if ( !objectID ) continue;
+
+        const char* childCountStr = ixmlElement_getAttribute( containerElement, "childCount" );
+        if ( !childCountStr ) continue;
+        int childCount = atoi( childCountStr );
+
+        const char* title = xml_getChildElementValue( containerElement, "dc:title" );
+        if ( !title ) continue;
+
+        const char* resource = xml_getChildElementValue( containerElement, "res" );
+
+        if ( resource && childCount < 1 )
+        {
+        Item* item = new Item( parent, objectID, title, resource );
+        parent->addItem( item );
+        }
+        else
+        {
+        Container* container = new Container( parent, objectID, title );
+        parent->addContainer( container );
+
+        if ( childCount > 0 ) _fetchContents( container );
+        }
+    }
+
+    ixmlNodeList_free( containerNodeList );
     }
 
     IXML_NodeList* itemNodeList = ixmlDocument_getElementsByTagName( result, "item" );
-    if ( itemNodeList ) 
+    if ( itemNodeList )
+    {
+        for ( unsigned int i = 0; i < ixmlNodeList_length( itemNodeList ); i++ )
     {
-       for ( unsigned int i = 0; i < ixmlNodeList_length( itemNodeList ); i++ ) 
-       {
-           IXML_Element* itemElement = ( IXML_Element* )ixmlNodeList_item( itemNodeList, i );
-       
-           const char* objectID = ixmlElement_getAttribute( itemElement, "id" );
-           if ( !objectID ) continue;
-      
-           const char* title = xml_getChildElementValue( itemElement, "dc:title" );
-           if ( !title ) continue;
-      
-           const char* resource = xml_getChildElementValue( itemElement, "res" );
-           if ( !resource ) continue;
-      
-           Item* item = new Item( parent, objectID, title, resource );
-           parent->addItem( item );
-       }
-    
-       ixmlNodeList_free( itemNodeList );
+        IXML_Element* itemElement = ( IXML_Element* )ixmlNodeList_item( itemNodeList, i );
+
+        const char* objectID = ixmlElement_getAttribute( itemElement, "id" );
+        if ( !objectID ) continue;
+
+        const char* title = xml_getChildElementValue( itemElement, "dc:title" );
+        if ( !title ) continue;
+
+        const char* resource = xml_getChildElementValue( itemElement, "res" );
+        if ( !resource ) continue;
+
+        Item* item = new Item( parent, objectID, title, resource );
+        parent->addItem( item );
+    }
+
+    ixmlNodeList_free( itemNodeList );
     }
-  
+
     ixmlDocument_free( result );
-  
+
     return true;
 }
 
-void MediaServer::_buildPlaylist( Container* parent ) 
-{ 
-    for ( unsigned int i = 0; i < parent->getNumContainers(); i++ ) 
+void MediaServer::_buildPlaylist( Container* parent )
+{
+    for ( unsigned int i = 0; i < parent->getNumContainers(); i++ )
     {
-       Container* container = parent->getContainer( i );
-       playlist_item_t* parentNode = parent->getPlaylistNode();
-
-       char* title = strdup( container->getTitle() );
-       playlist_item_t* node = playlist_NodeCreate( _cookie->serviceDiscovery->p_sys->p_playlist,
-                                                    VIEW_CATEGORY,
-                                                    title,
-                                                    parentNode );
-       free( title );
-       
-       container->setPlaylistNode( node );
-       _buildPlaylist( container );
+    Container* container = parent->getContainer( i );
+    playlist_item_t* parentNode = parent->getPlaylistNode();
+
+    char* title = strdup( container->getTitle() );
+    playlist_item_t* node = playlist_NodeCreate( _cookie->serviceDiscovery->p_sys->p_playlist,
+                             VIEW_CATEGORY,
+                             title,
+                             parentNode );
+    free( title );
+
+    container->setPlaylistNode( node );
+    _buildPlaylist( container );
     }
 
-    for ( unsigned int i = 0; i < parent->getNumItems(); i++ ) 
+    for ( unsigned int i = 0; i < parent->getNumItems(); i++ )
     {
-       Item* item = parent->getItem( i );
-       playlist_item_t* parentNode = parent->getPlaylistNode();
-
-       playlist_item_t* node = playlist_ItemNew( _cookie->serviceDiscovery, 
-                                                item->getResource(), 
-                                                item->getTitle() );
-    
-       playlist_NodeAddItem( _cookie->serviceDiscovery->p_sys->p_playlist, 
-                            node, 
-                            VIEW_CATEGORY,
-                            parentNode, PLAYLIST_APPEND, PLAYLIST_END );
-
-       item->setPlaylistNode( node );
+    Item* item = parent->getItem( i );
+    playlist_item_t* parentNode = parent->getPlaylistNode();
+
+    playlist_item_t* node = playlist_ItemNew( _cookie->serviceDiscovery,
+                         item->getResource(),
+                         item->getTitle() );
+
+    playlist_NodeAddItem( _cookie->serviceDiscovery->p_sys->p_playlist,
+                 node,
+                 VIEW_CATEGORY,
+                 parentNode, PLAYLIST_APPEND, PLAYLIST_END );
+
+    item->setPlaylistNode( node );
     }
 }
 
-void MediaServer::setPlaylistNode( playlist_item_t* playlistNode ) 
+void MediaServer::setPlaylistNode( playlist_item_t* playlistNode )
 {
     _playlistNode = playlistNode;
 }
 
-bool MediaServer::compareSID( const char* sid ) 
+bool MediaServer::compareSID( const char* sid )
 {
     return ( strncmp( _subscriptionID, sid, sizeof( Upnp_SID ) ) == 0 );
 }
@@ -966,20 +909,20 @@ bool MediaServer::compareSID( const char* sid )
 
 // MediaServerList...
 
-MediaServerList::MediaServerList( Cookie* cookie ) 
+MediaServerList::MediaServerList( Cookie* cookie )
 {
     _cookie = cookie;
 }
 
-MediaServerList::~MediaServerList() 
+MediaServerList::~MediaServerList()
 {
     for ( unsigned int i = 0; i < _list.size(); i++ )
     {
-       delete _list[i];
+    delete _list[i];
     }
 }
 
-bool MediaServerList::addServer( MediaServer* s ) 
+bool MediaServerList::addServer( MediaServer* s )
 {
     if ( getServer( s->getUDN() ) != 0 ) return false;
 
@@ -989,73 +932,73 @@ bool MediaServerList::addServer( MediaServer* s )
 
     char* name = strdup( s->getFriendlyName() );
     playlist_item_t* node = playlist_NodeCreate( _cookie->serviceDiscovery->p_sys->p_playlist,
-                                               VIEW_CATEGORY,
-                                               name,
-                                               _cookie->serviceDiscovery->p_sys->p_node );
+                        VIEW_CATEGORY,
+                        name,
+                        _cookie->serviceDiscovery->p_sys->p_node );
     free( name );
     s->setPlaylistNode( node );
 
     return true;
 }
 
-MediaServer* MediaServerList::getServer( const char* UDN ) 
+MediaServer* MediaServerList::getServer( const char* UDN )
 {
     MediaServer* result = 0;
-    
-    for ( unsigned int i = 0; i < _list.size(); i++ ) 
+
+    for ( unsigned int i = 0; i < _list.size(); i++ )
+    {
+        if( strcmp( UDN, _list[i]->getUDN() ) == 0 )
     {
-       if( strcmp( UDN, _list[i]->getUDN() ) == 0 ) 
-       { 
-           result = _list[i]; 
-           break;
-       }
+        result = _list[i];
+        break;
+    }
     }
 
     return result;
 }
 
-MediaServer* MediaServerList::getServerBySID( const char* sid ) 
-{ 
+MediaServer* MediaServerList::getServerBySID( const char* sid )
+{
     MediaServer* server = 0;
 
-    for ( unsigned int i = 0; i < _list.size(); i++ ) 
+    for ( unsigned int i = 0; i < _list.size(); i++ )
+    {
+    if ( _list[i]->compareSID( sid ) )
     {
-       if ( _list[i]->compareSID( sid ) ) 
-       { 
-           server = _list[i];
-           break;
-       }
+        server = _list[i];
+        break;
     }
-  
+    }
+
     return server;
 }
 
-void MediaServerList::removeServer( const char* UDN ) 
+void MediaServerList::removeServer( const char* UDN )
 {
     MediaServer* server = getServer( UDN );
     if ( !server ) return;
 
-    msg_Dbg( _cookie->serviceDiscovery, "Removing server '%s'", server->getFriendlyName() );  
+    msg_Dbg( _cookie->serviceDiscovery, "Removing server '%s'", server->getFriendlyName() );
 
     std::vector<MediaServer*>::iterator it;
-    for ( it = _list.begin(); it != _list.end(); it++ ) 
+    for ( it = _list.begin(); it != _list.end(); it++ )
     {
-       if ( *it == server ) 
-       {
-           _list.erase( it );
-           delete server;
-           break;
-       }
+        if ( *it == server )
+    {
+              _list.erase( it );
+        delete server;
+        break;
+    }
     }
 }
 
 
 // Item...
 
-Item::Item( Container* parent, const char* objectID, const char* title, const char* resource ) 
+Item::Item( Container* parent, const char* objectID, const char* title, const char* resource )
 {
     _parent = parent;
-  
+
     _objectID = objectID;
     _title = title;
     _resource = resource;
@@ -1063,105 +1006,105 @@ Item::Item( Container* parent, const char* objectID, const char* title, const ch
     _playlistNode = 0;
 }
 
-const char* Item::getObjectID() const 
+const char* Item::getObjectID() const
 {
     return _objectID.c_str();
 }
 
-const char* Item::getTitle() const 
+const char* Item::getTitle() const
 {
     return _title.c_str();
 }
 
-const char* Item::getResource() const 
+const char* Item::getResource() const
 {
     return _resource.c_str();
 }
 
-void Item::setPlaylistNode( playlist_item_t* node ) 
-{ 
-    _playlistNode = node; 
+void Item::setPlaylistNode( playlist_item_t* node )
+{
+    _playlistNode = node;
 }
 
-playlist_item_t* Item::getPlaylistNode() const 
-{ 
-    return _playlistNode; 
+playlist_item_t* Item::getPlaylistNode() const
+{
+    return _playlistNode;
 }
 
 
 // Container...
 
-Container::Container( Container* parent, const char* objectID, const char* title ) 
+Container::Container( Container* parent, const char* objectID, const char* title )
 {
     _parent = parent;
-    
+
     _objectID = objectID;
     _title = title;
 
     _playlistNode = 0;
 }
 
-Container::~Container() 
+Container::~Container()
 {
-    for ( unsigned int i = 0; i < _containers.size(); i++ ) 
+    for ( unsigned int i = 0; i < _containers.size(); i++ )
     {
-       delete _containers[i];
+    delete _containers[i];
     }
 
-    for ( unsigned int i = 0; i < _items.size(); i++ ) 
+    for ( unsigned int i = 0; i < _items.size(); i++ )
     {
-       delete _items[i];
+    delete _items[i];
     }
 }
 
-void Container::addItem( Item* item ) 
+void Container::addItem( Item* item )
 {
     _items.push_back( item );
 }
 
-void Container::addContainer( Container* container ) 
+void Container::addContainer( Container* container )
 {
     _containers.push_back( container );
 }
 
-const char* Container::getObjectID() const 
-{ 
-    return _objectID.c_str(); 
+const char* Container::getObjectID() const
+{
+    return _objectID.c_str();
 }
 
-const char* Container::getTitle() const 
-{ 
-    return _title.c_str(); 
+const char* Container::getTitle() const
+{
+    return _title.c_str();
 }
 
-unsigned int Container::getNumItems() const 
-{ 
-    return _items.size(); 
+unsigned int Container::getNumItems() const
+{
+    return _items.size();
 }
 
-unsigned int Container::getNumContainers() const 
-{ 
-    return _containers.size(); 
+unsigned int Container::getNumContainers() const
+{
+    return _containers.size();
 }
 
-Item* Container::getItem( unsigned int i ) const 
+Item* Container::getItem( unsigned int i ) const
 {
     if ( i < _items.size() ) return _items[i];
     return 0;
 }
 
-Container* Container::getContainer( unsigned int i ) const 
+Container* Container::getContainer( unsigned int i ) const
 {
     if ( i < _containers.size() ) return _containers[i];
     return 0;
 }
 
-void Container::setPlaylistNode( playlist_item_t* node ) 
-{ 
-    _playlistNode = node; 
+void Container::setPlaylistNode( playlist_item_t* node )
+{
+    _playlistNode = node;
 }
 
-playlist_item_t* Container::getPlaylistNode() const 
-{ 
-    return _playlistNode; 
+playlist_item_t* Container::getPlaylistNode() const
+{
+    return _playlistNode;
 }
index 038b3e3..a284988 100644 (file)
@@ -89,6 +89,7 @@ HEADERS_include = \
        ../include/vlc_playlist.h \
        ../include/vlc_spu.h \
        ../include/vlc_stream.h \
+       ../include/vlc_strings.h \
        ../include/vlc_symbols.h \
        ../include/vlc_threads_funcs.h \
        ../include/vlc_threads.h \
@@ -321,6 +322,7 @@ SOURCES_libvlc_common = \
        misc/modules.c \
        misc/threads.c \
        misc/stats.c \
+       misc/strings.c \
        misc/unicode.c \
        misc/cpu.c \
        misc/configuration.c \
index e59b55c..ec20d48 100644 (file)
 #include "vlc_osd.h"
 
 #include "vlc_update.h"
+#include "vlc_strings.h"
 
 #if defined( _MSC_VER ) && defined( UNDER_CE )
 #    include "modules_builtin_evc.h"
diff --git a/src/misc/strings.c b/src/misc/strings.c
new file mode 100644 (file)
index 0000000..645f355
--- /dev/null
@@ -0,0 +1,179 @@
+/*****************************************************************************
+ * strings.c: String related functions
+ *****************************************************************************
+ * Copyright (C) 2006 the VideoLAN team
+ * $Id$
+ *
+ * Authors: Antoine Cellerier <dionoea at videolan dot org>
+ *          Daniel Stranger <vlc at schmaller dot de>
+ *
+ * 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.
+ *****************************************************************************/
+
+/*****************************************************************************
+ * Preamble
+ *****************************************************************************/
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "vlc_strings.h"
+
+/**
+ * Decode URI encoded string
+ * \return decoded duplicated string
+ */
+char *decode_encoded_URI_duplicate( const char *psz )
+{
+    char *psz_dup = strdup( psz );
+    decode_encoded_URI( psz_dup );
+    return psz_dup;
+}
+
+/**
+ * Decode URI encoded string
+ * \return nothing
+ */
+void decode_encoded_URI( char *psz )
+{
+    char *dup = strdup( psz );
+    char *p = dup;
+
+    while( *p )
+    {
+        if( *p == '%' )
+        {
+            char val[3];
+            p++;
+            if( !*p )
+            {
+                break;
+            }
+
+            val[0] = *p++;
+            val[1] = *p++;
+            val[2] = '\0';
+
+            *psz++ = strtol( val, NULL, 16 );
+        }
+        else if( *p == '+' )
+        {
+            *psz++ = ' ';
+            p++;
+        }
+        else
+        {
+            *psz++ = *p++;
+        }
+    }
+    *psz++ = '\0';
+    free( dup );
+}
+
+/**
+ * Converts "&lt;", "&gt;" and "&amp;" to "<", ">" and "&"
+ * \param string to convert
+ */
+void resolve_xml_special_chars( char *psz_value )
+{
+    char *p_pos = psz_value;
+
+    while ( *psz_value )
+    {
+        if( !strncmp( psz_value, "&lt;", 4 ) )
+        {
+            *p_pos = '<';
+            psz_value += 4;
+        }
+        else if( !strncmp( psz_value, "&gt;", 4 ) )
+        {
+            *p_pos = '>';
+            psz_value += 4;
+        }
+        else if( !strncmp( psz_value, "&amp;", 5 ) )
+        {
+            *p_pos = '&';
+            psz_value += 5;
+        }
+        else if( !strncmp( psz_value, "&quot;", 6 ) )
+        {
+            *p_pos = '\"';
+            psz_value += 6;
+        }
+        else if( !strncmp( psz_value, "&#039;", 6 ) )
+        {
+            *p_pos = '\'';
+            psz_value += 6;
+        }
+        else
+        {
+            *p_pos = *psz_value;
+            psz_value++;
+        }
+
+        p_pos++;
+    }
+
+    *p_pos = '\0';
+}
+
+/**
+ * Converts '<', '>', '\"', '\'' and '&' to their html entities
+ * \param psz_content simple element content that is to be converted
+ */
+char *convert_xml_special_chars( const char *psz_content )
+{
+    char *psz_temp = malloc( 6 * strlen( psz_content ) + 1 );
+    const char *p_from = psz_content;
+    char *p_to   = psz_temp;
+
+    while ( *p_from )
+    {
+        if ( *p_from == '<' )
+        {
+            strcpy( p_to, "&lt;" );
+            p_to += 4;
+        }
+        else if ( *p_from == '>' )
+        {
+            strcpy( p_to, "&gt;" );
+            p_to += 4;
+        }
+        else if ( *p_from == '&' )
+        {
+            strcpy( p_to, "&amp;" );
+            p_to += 5;
+        }
+        else if( *p_from == '\"' )
+        {
+            strcpy( p_to, "&quot;" );
+            p_to += 6;
+        }
+        else if( *p_from == '\'' )
+        {
+            strcpy( p_to, "&#039;" );
+            p_to += 6;
+        }
+        else
+        {
+            *p_to = *p_from;
+            p_to++;
+        }
+        p_from++;
+    }
+    *p_to = '\0';
+
+    return psz_temp;
+}
index c3684fb..651d44a 100644 (file)
@@ -127,6 +127,9 @@ int playlist_Export( playlist_t * p_playlist, const char *psz_filename ,
         msg_Err( p_playlist, "out of memory");
         return VLC_ENOMEM;
     }
+    p_export->psz_filename = NULL;
+    if ( psz_filename )
+        p_export->psz_filename = strdup( psz_filename );
     p_export->p_file = utf8_fopen( psz_filename, "wt" );
     if( !p_export->p_file )
     {
@@ -149,8 +152,12 @@ int playlist_Export( playlist_t * p_playlist, const char *psz_filename ,
     }
     module_Unneed( p_playlist , p_module );
 
+    /* Clean up */
     fclose( p_export->p_file );
-
+    if ( p_export->psz_filename )
+        free( p_export->psz_filename );
+    free ( p_export );
+    p_playlist->p_private = NULL;
     vlc_mutex_unlock( &p_playlist->object_lock );
 
     return VLC_SUCCESS;