* Added Continuous Media Markup Language (CMML) codec
authorAndre Pang <andrep@videolan.org>
Mon, 19 Apr 2004 02:08:52 +0000 (02:08 +0000)
committerAndre Pang <andrep@videolan.org>
Mon, 19 Apr 2004 02:08:52 +0000 (02:08 +0000)
18 files changed:
configure.ac
modules/LIST
modules/codec/cmml/Modules.am [new file with mode: 0644]
modules/codec/cmml/browser_open.c [new file with mode: 0644]
modules/codec/cmml/browser_open.h [new file with mode: 0644]
modules/codec/cmml/cmml.c [new file with mode: 0644]
modules/codec/cmml/history.c [new file with mode: 0644]
modules/codec/cmml/history.h [new file with mode: 0644]
modules/codec/cmml/intf.c [new file with mode: 0644]
modules/codec/cmml/xarray.c [new file with mode: 0644]
modules/codec/cmml/xarray.h [new file with mode: 0644]
modules/codec/cmml/xlist.c [new file with mode: 0644]
modules/codec/cmml/xlist.h [new file with mode: 0644]
modules/codec/cmml/xstrcat.h [new file with mode: 0644]
modules/codec/cmml/xtag.c [new file with mode: 0644]
modules/codec/cmml/xtag.h [new file with mode: 0644]
modules/codec/cmml/xurl.c [new file with mode: 0644]
modules/codec/cmml/xurl.h [new file with mode: 0644]

index 991c40a..42fbc82 100644 (file)
@@ -2346,6 +2346,16 @@ AX_ADD_LDFLAGS([subsdec],[${LIBICONV}])
 AX_ADD_CPPFLAGS([subsdec],[${INCICONV}])
 AX_ADD_PLUGINS([subsdec])
 
+dnl
+dnl  CMML plugin
+dnl
+AC_ARG_ENABLE(cmml,
+  [  --enable-cmml           CMML support (default enabled)])
+if test "${enable_cmml}" != "no"
+then
+  AX_ADD_PLUGINS([cmml])
+fi
+
 
 dnl
 dnl  Video Filters
@@ -3776,6 +3786,7 @@ AC_CONFIG_FILES([
   modules/audio_mixer/Makefile
   modules/audio_output/Makefile
   modules/codec/Makefile
+  modules/codec/cmml/Makefile
   modules/codec/ffmpeg/Makefile
   modules/codec/ffmpeg/postprocessing/Makefile
   modules/codec/ogt/Makefile
index 9f68c9e..c13ea53 100644 (file)
@@ -1,5 +1,5 @@
 List of vlc plugins (221)
-$Id: LIST,v 1.19 2004/01/05 13:07:02 zorglub Exp $
+$Id$
 
  * a52: A/52 basic parser/packetizer
 
@@ -70,6 +70,8 @@ $Id: LIST,v 1.19 2004/01/05 13:07:02 zorglub Exp $
  * cinepak: Cinepack video decoder
 
  * clone: Clone video filter
+ * cmml: Continuous Media Markup Language annotations/hyperlinks decoder
 
  * corba: CORBA control module
 
diff --git a/modules/codec/cmml/Modules.am b/modules/codec/cmml/Modules.am
new file mode 100644 (file)
index 0000000..27747cc
--- /dev/null
@@ -0,0 +1,11 @@
+SOURCES_cmml = \
+       browser_open.c browser_open.h \
+       cmml.c \
+       history.c history.h \
+       intf.c \
+       xarray.c xarray.h \
+       xlist.c xlist.h \
+       xstrcat.h \
+       xtag.c xtag.h \
+       xurl.c xurl.h
+
diff --git a/modules/codec/cmml/browser_open.c b/modules/codec/cmml/browser_open.c
new file mode 100644 (file)
index 0000000..9ac0670
--- /dev/null
@@ -0,0 +1,67 @@
+/*****************************************************************************
+ * browser_open.c: platform-independent opening of a web browser
+ *****************************************************************************
+ * Copyright (C) 2004 Commonwealth Scientific and Industrial Research
+ *                    Organisation (CSIRO) Australia
+ * Copyright (C) 2004 VideoLAN
+ *
+ * $Id$
+ *
+ * Authors: Andre Pang <Andre.Pang@csiro.au>
+ *
+ * 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., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
+ *****************************************************************************/
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "xstrcat.h"
+
+int browser_Open( char *psz_url )
+{
+#ifdef SYS_DARWIN
+    char *psz_open_commandline;
+
+    psz_open_commandline = strdup( "open " );
+    xstrcat( psz_open_commandline, psz_url );
+        
+    return system( psz_open_commandline );
+#elif defined( WIN32 )
+    char *psz_open_commandline;
+
+    psz_open_commandline = strdup( "explorer " );
+    xstrcat( psz_open_commandline, psz_url );
+
+    return system( psz_open_commandline );
+#else
+    /* Assume we're on a UNIX of some sort */
+    char *psz_open_commandline;
+
+    /* Debian uses www-browser */
+    psz_open_commandline = strdup( "www-browser" );
+    xstrcat( psz_open_commandline, psz_url );
+
+    if( system( psz_open_commandline ) != 0 )
+    {
+        free( psz_open_commandline );
+
+        /* Try mozilla */
+        psz_open_commandline = strdup( "mozilla" );
+        xstrcat( psz_open_commandline, psz_url );
+        return system( psz_open_commandline );
+    }
+#endif
+}
+
diff --git a/modules/codec/cmml/browser_open.h b/modules/codec/cmml/browser_open.h
new file mode 100644 (file)
index 0000000..cee25a5
--- /dev/null
@@ -0,0 +1,33 @@
+/*****************************************************************************
+ * browser_open.h: platform-independent opening of a web browser
+ *****************************************************************************
+ * Copyright (C) 2004 Commonwealth Scientific and Industrial Research
+ *                    Organisation (CSIRO) Australia
+ * Copyright (C) 2004 VideoLAN
+ *
+ * $Id$
+ *
+ * Authors: Andre Pang <Andre.Pang@csiro.au>
+ *
+ * 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., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
+ *****************************************************************************/
+
+#ifndef __BROWSER_OPEN_H__
+#define __BROWSER_OPEN_H__
+
+int browser_Open( char *psz_url );
+
+#endif /* __BROWSER_OPEN_H__ */
+
diff --git a/modules/codec/cmml/cmml.c b/modules/codec/cmml/cmml.c
new file mode 100644 (file)
index 0000000..9650d0b
--- /dev/null
@@ -0,0 +1,284 @@
+/*****************************************************************************
+ * cmml.c : CMML annotations/metadata decoder
+ *****************************************************************************
+ * Copyright (C) 2003-2004 Commonwealth Scientific and Industrial Research
+ *                         Organisation (CSIRO) Australia
+ * Copyright (C) 2004 VideoLAN
+ *
+ * $Id$
+ *
+ * Author: Andre Pang <Andre.Pang@csiro.au>
+ *
+ * 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., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
+ *****************************************************************************/
+
+/*****************************************************************************
+ * Preamble
+ *****************************************************************************/
+#include <vlc/vlc.h>
+#include <vlc/decoder.h>
+#include <vlc/intf.h>
+
+#include <osd.h>
+
+#include "charset.h"
+
+#include "xtag.h"
+
+#undef CMML_DEBUG
+
+/*****************************************************************************
+ * decoder_sys_t : decoder descriptor
+ *****************************************************************************/
+struct decoder_sys_t
+{
+    intf_thread_t *     p_intf;
+};
+
+/*****************************************************************************
+ * Local prototypes
+ *****************************************************************************/
+static int  OpenDecoder   ( vlc_object_t * );
+static void CloseDecoder  ( vlc_object_t * );
+
+static void DecodeBlock   ( decoder_t *, block_t ** );
+
+static void ParseText     ( decoder_t *, block_t * );
+
+/*****************************************************************************
+ * Exported prototypes
+ *****************************************************************************/
+int  E_(OpenIntf)  ( vlc_object_t * );
+void E_(CloseIntf) ( vlc_object_t * );
+
+/*****************************************************************************
+ * Module descriptor.
+ *****************************************************************************/
+vlc_module_begin();
+    set_description( _("CMML annotations decoder") );
+    set_capability( "decoder", 50 );
+    set_callbacks( OpenDecoder, CloseDecoder );
+    add_shortcut( "cmml" );
+
+    add_submodule();
+        set_capability( "interface", 0 );
+        set_callbacks( E_(OpenIntf), E_(CloseIntf) );
+vlc_module_end();
+
+/*****************************************************************************
+ * OpenDecoder: probe the decoder and return score
+ *****************************************************************************
+ * Tries to launch a decoder and return score so that the interface is able
+ * to chose.
+ *****************************************************************************/
+static int OpenDecoder( vlc_object_t *p_this )
+{
+    decoder_t *p_dec = (decoder_t*)p_this;
+    input_thread_t * p_input;
+    decoder_sys_t *p_sys;
+    vlc_value_t val;
+
+    if( p_dec->fmt_in.i_codec != VLC_FOURCC('c','m','m','l') )
+    {
+        return VLC_EGENERIC;
+    }
+
+    p_dec->pf_decode_sub = DecodeBlock;
+
+#ifdef CMML_DEBUG
+    msg_Dbg( p_dec, "I am at %p", p_dec );
+#endif
+
+    /* Allocate the memory needed to store the decoder's structure */
+    if( ( p_dec->p_sys = p_sys =
+          (decoder_sys_t *)malloc(sizeof(decoder_sys_t)) ) == NULL )
+    {
+        msg_Err( p_dec, "out of memory" );
+        return VLC_EGENERIC;
+    }
+
+    /* Let other interested modules know that we're a CMML decoder
+     * We have to set this variable on the input thread, because there's
+     * typically more than one decoder running so we can't find the CMML
+     * decoder succesfully with vlc_object_find.  (Any hints on how to achieve
+     * this would be rather appreciated ;) */
+    p_input = vlc_object_find( p_dec, VLC_OBJECT_INPUT, FIND_ANYWHERE );
+#ifdef CMML_DEBUG
+    msg_Dbg( p_dec, "p_input is at %p", p_input );
+#endif
+    val.p_address = p_dec;
+    var_Create( p_input, "has-cmml-decoder",
+                VLC_VAR_ADDRESS|VLC_VAR_DOINHERIT );
+    if( var_Set( p_input, "has-cmml-decoder", val ) != VLC_SUCCESS )
+    {
+        msg_Dbg( p_dec, "var_Set of has-cmml-decoder failed" );
+    }
+    vlc_object_release( p_input );
+
+    /* initialise the CMML responder interface */
+    p_sys->p_intf = intf_Create( p_dec, "cmml" );
+    p_sys->p_intf->b_block = VLC_FALSE;
+    intf_RunThread( p_sys->p_intf );
+
+    return VLC_SUCCESS;
+}
+
+/****************************************************************************
+ * DecodeBlock: the whole thing
+ ****************************************************************************
+ * This function must be fed with complete subtitles units.
+ ****************************************************************************/
+static void DecodeBlock( decoder_t *p_dec, block_t **pp_block )
+{
+    if( !pp_block || *pp_block == NULL )
+    {
+        return;
+    }
+
+    ParseText( p_dec, *pp_block );
+
+    block_Release( *pp_block );
+    *pp_block = NULL;
+}
+
+/*****************************************************************************
+ * CloseDecoder: clean up the decoder
+ *****************************************************************************/
+static void CloseDecoder( vlc_object_t *p_this )
+{
+    decoder_t *p_dec = (decoder_t *)p_this;
+    decoder_sys_t *p_sys = p_dec->p_sys;
+    intf_thread_t *p_intf;
+
+    /* Destroy the interface object/thread */
+    p_intf = vlc_object_find( p_dec, VLC_OBJECT_INTF, FIND_CHILD );
+    if( p_intf != NULL )
+    {
+#ifdef CMML_DEBUG
+        msg_Dbg( p_dec, "CMML decoder is freeing interface thread" );
+#endif
+        intf_StopThread( p_intf );
+        vlc_object_detach( p_intf );
+        vlc_object_release( p_intf );
+        intf_Destroy( p_intf );
+    }
+
+    p_sys->p_intf = NULL;
+
+    free( p_sys );
+}
+
+/*****************************************************************************
+ * ParseText: parse an text subtitle packet and send it to the video output
+ *****************************************************************************/
+static void ParseText( decoder_t *p_dec, block_t *p_block )
+{
+    char *psz_subtitle, *psz_cmml, *psz_url;
+    XTag *p_clip_parser, *p_anchor;
+    vlc_value_t val;
+
+    /* We cannot display a subpicture with no date */
+    if( p_block->i_pts == 0 )
+    {
+        msg_Warn( p_dec, "subtitle without a date" );
+        return;
+    }
+
+    /* Check validity of packet data */
+    if( p_block->i_buffer <= 1 ||  p_block->p_buffer[0] == '\0' )
+    {
+        msg_Warn( p_dec, "empty subtitle" );
+        return;
+    }
+
+    /* get anchor text from CMML */
+
+    /* Copy the whole CMML tag into our own buffer:
+       allocate i_buffer bytes + 1 for the terminating \0 */
+    if ( (psz_cmml = malloc( p_block->i_buffer + 1 )) == NULL )
+        return;
+    psz_cmml = memcpy( psz_cmml, p_block->p_buffer, p_block->i_buffer );
+    psz_cmml[p_block->i_buffer] = '\0'; /* terminate the string */
+#ifdef CMML_DEBUG
+    msg_Dbg( p_dec, "psz_cmml is \"%s\"", psz_cmml );
+#endif
+    
+    /* Parse the <clip> part of the CMML */
+    p_clip_parser = xtag_new_parse( psz_cmml, p_block->i_buffer );
+    if( !p_clip_parser )
+    {
+        msg_Warn( p_dec, "couldn't initialise <clip> parser" );
+        free( psz_cmml );
+        return;
+    }
+
+    /* Parse the anchor tag and get its contents */
+    p_anchor = xtag_first_child( p_clip_parser, "a" );
+    if( p_anchor != NULL )
+    {
+        psz_subtitle = xtag_get_pcdata( p_anchor );
+    }
+    else
+    {
+        psz_subtitle = strdup( " " );
+    }
+
+#ifdef CMML_DEBUG
+    msg_Dbg( p_dec, "psz_subtitle is \"%s\"", psz_subtitle );
+#endif
+
+    /* get URL from the current clip, if one exists */
+    psz_url = xtag_get_attribute( p_anchor, "href" );
+#ifdef CMML_DEBUG
+    msg_Dbg( p_dec, "psz_url is \"%s\"", psz_url );
+#endif
+    if( psz_url )
+    {
+        char *psz_tmp = strdup( psz_url );
+        
+        val.p_address = psz_tmp;
+        if( var_Set( p_dec, "psz-current-anchor-url", val ) != VLC_SUCCESS )
+        {
+            (void) var_Create( p_dec, "psz-current-anchor-url",
+                               VLC_VAR_ADDRESS|VLC_VAR_DOINHERIT );
+            msg_Dbg( p_dec, "creating psz-current-anchor-url" );
+            if( var_Set( p_dec, "psz-current-anchor-url", val ) != VLC_SUCCESS )
+                msg_Dbg( p_dec, "var_Set of psz-current-anchor-url failed" );
+        }
+    }
+
+    if( psz_subtitle )
+    {
+        char *psz_tmp = strdup( psz_subtitle );
+
+        val.p_address = psz_tmp;
+        if( var_Set( p_dec, "psz-current-anchor-description", val ) != VLC_SUCCESS )
+        {
+            (void) var_Create( p_dec, "psz-current-anchor-description",
+                               VLC_VAR_ADDRESS|VLC_VAR_DOINHERIT );
+            msg_Dbg( p_dec, "creating psz-current-anchor-description" );
+            if( var_Set( p_dec, "psz-current-anchor-description", val ) != VLC_SUCCESS )
+                msg_Dbg( p_dec, "var_Set of psz-current-anchor-description failed" );
+        }
+
+    }
+
+    if( psz_subtitle ) free( psz_subtitle );
+    if( psz_cmml ) free( psz_cmml );
+    if( p_anchor ) free( p_anchor );
+    if( p_clip_parser ) free( p_clip_parser );
+    if( psz_url ) free( psz_url );
+}
+
diff --git a/modules/codec/cmml/history.c b/modules/codec/cmml/history.c
new file mode 100644 (file)
index 0000000..ae57503
--- /dev/null
@@ -0,0 +1,228 @@
+/*****************************************************************************
+ * history.c: vlc_history_t (web-browser-like back/forward history) handling
+ *****************************************************************************
+ * Copyright (C) 2004 Commonwealth Scientific and Industrial Research
+ *                    Organisation (CSIRO) Australia
+ * Copyright (C) 2004 VideoLAN
+ *
+ * $Id$
+ *
+ * Authors: Andre Pang <Andre.Pang@csiro.au>
+ *
+ * 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., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
+ *****************************************************************************/
+
+#include <vlc/vlc.h>
+
+#include "history.h"
+
+#include "xarray.h"
+
+#ifdef HAVE_STDLIB_H
+#   include <stdlib.h>                                          /* realloc() */
+#endif
+
+#undef HISTORY_DEBUG
+
+/*****************************************************************************
+ * Local prototypes
+ *****************************************************************************/
+static void history_Dump( history_t *p_history );
+
+/*****************************************************************************
+ * Local structure lock
+ *****************************************************************************/
+
+/*****************************************************************************
+ * Actual history code
+ *****************************************************************************/
+
+history_t *history_New()
+{
+   history_t *p_new_history;
+   
+   p_new_history = calloc( 1, sizeof( struct history_t ) );
+   if( p_new_history == NULL ) return NULL;
+
+   p_new_history->p_xarray = xarray_New( 0 );
+   if( p_new_history->p_xarray == NULL )
+   {
+       free( p_new_history );
+       return NULL;
+   }
+
+#ifndef HISTORY_DEBUG
+   /* make dummy reference to history_Dump to avoid compiler warnings */
+   while (0)
+   {
+       void *p_tmp;
+
+       p_tmp = history_Dump;
+   }
+#endif
+
+   return p_new_history;
+}
+
+vlc_bool_t history_GoBackSavingCurrentItem ( history_t *p_history,
+                                             history_item_t *p_item )
+{
+    history_PruneAndInsert( p_history, p_item );
+
+    /* PruneAndInsert will increment the index, so we need to go
+     * back one position to reset the index to the place we were at
+     * before saving the current state, and then go back one more to
+     * actually go back */
+    p_history->i_index -= 2;
+
+#ifdef HISTORY_DEBUG
+    history_Dump( p_history );
+#endif
+    return VLC_TRUE;
+}
+
+static void history_Dump( history_t *p_history )
+{
+    unsigned int i_count;
+    int i;
+
+    if( xarray_Count( p_history->p_xarray, &i_count ) != XARRAY_SUCCESS )
+        return;
+
+    for (i = 0; i < (int) i_count; i++)
+    {
+        history_item_t *p_item;
+        void *pv_item;
+
+        xarray_ObjectAtIndex( p_history->p_xarray, i, &pv_item );
+        
+        p_item = (history_item_t *) pv_item;
+
+        if( p_item == NULL )
+            fprintf( stderr, "HISTORY: [%d] NULL\n", i );
+        else
+            fprintf( stderr, "HISTORY: [%d] %p (%p->%s)\n", i, p_item,
+                     p_item->psz_uri, p_item->psz_uri );
+    }
+}
+
+vlc_bool_t history_GoForwardSavingCurrentItem ( history_t *p_history,
+                                                history_item_t *p_item )
+{
+#ifdef HISTORY_DEBUG
+    history_Dump( p_history );
+#endif
+
+    if( xarray_ReplaceObject( p_history->p_xarray, p_history->i_index, p_item )
+        == XARRAY_SUCCESS )
+    {
+        p_history->i_index++;
+        return VLC_TRUE;
+    }
+    else
+    {
+        return VLC_FALSE;
+    }
+}
+
+vlc_bool_t history_CanGoBack( history_t *p_history )
+{
+    if( p_history->i_index > 0 )
+        return VLC_TRUE;
+    else
+        return VLC_FALSE;
+}
+
+vlc_bool_t history_CanGoForward( history_t *p_history )
+{
+    unsigned int i_count;
+
+    if( xarray_Count( p_history->p_xarray, &i_count ) != XARRAY_SUCCESS )
+        return VLC_FALSE;
+
+    if( p_history->i_index < i_count )
+        return VLC_TRUE;
+    else
+        return VLC_FALSE;
+}
+
+history_item_t *history_Item( history_t *p_history )
+{
+    history_item_t *p_item;
+    void *pv_item;
+
+    if( xarray_ObjectAtIndex( p_history->p_xarray, p_history->i_index,
+                              &pv_item )
+        == XARRAY_SUCCESS )
+    {
+        p_item = (history_item_t *) pv_item;
+        return p_item;
+    }
+    else
+    {
+        return NULL;
+    }
+}
+
+void history_Prune( history_t *p_history )
+{
+    xarray_RemoveObjectsAfter( p_history->p_xarray, p_history->i_index );
+    xarray_RemoveObject( p_history->p_xarray, p_history->i_index );
+}
+
+void history_PruneAndInsert( history_t *p_history, history_item_t *p_item )
+{
+    unsigned int i_count;
+
+    xarray_Count( p_history->p_xarray, &i_count );
+
+    if( i_count == 0 )
+    {
+        xarray_InsertObject( p_history->p_xarray, p_item, 0 );
+        p_history->i_index = 1;
+    }
+    else
+    {
+        history_Prune( p_history );
+        xarray_InsertObject( p_history->p_xarray, p_item, p_history->i_index );
+        p_history->i_index++;
+    }
+}
+
+unsigned int history_Count( history_t *p_history )
+{
+    int i_count;
+    xarray_Count( p_history->p_xarray, &i_count );
+    return i_count;
+}
+
+unsigned int history_Index( history_t *p_history )
+{
+    return p_history->i_index;
+}
+
+history_item_t * historyItem_New( char *psz_name, char *psz_uri )
+{
+    history_item_t *p_history_item = NULL;
+
+    p_history_item = (history_item_t *) malloc( sizeof(history_item_t) );
+    if( !p_history_item ) return NULL;
+
+    p_history_item->psz_uri = strdup( psz_uri );
+    p_history_item->psz_name = strdup( psz_name );
+
+    return p_history_item;
+}
+
diff --git a/modules/codec/cmml/history.h b/modules/codec/cmml/history.h
new file mode 100644 (file)
index 0000000..63f0556
--- /dev/null
@@ -0,0 +1,69 @@
+/*****************************************************************************
+ * history.h: vlc_history_t (web-browser-like back/forward history) handling
+ *****************************************************************************
+ * Copyright (C) 2004 Commonwealth Scientific and Industrial Research
+ *                    Organisation (CSIRO) Australia
+ * Copyright (C) 2004 VideoLAN
+ *
+ * $Id$
+ *
+ * Authors: Andre Pang <Andre.Pang@csiro.au>
+ *
+ * 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., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
+ *****************************************************************************/
+
+#ifndef __VLC_HISTORY_H__
+#define __VLC_HISTORY_H__
+
+#define XARRAY_EMBED_IN_HOST_C_FILE
+#include "xarray.h"
+
+struct history_item_t
+{
+    char * psz_name;
+    char * psz_uri;
+};
+
+struct history_t
+{
+    unsigned int i_index; /* current index into history */
+    XArray * p_xarray;
+};
+
+typedef struct history_item_t history_item_t;
+typedef struct history_t history_t;
+
+
+/*****************************************************************************
+ * Exported prototypes
+ *****************************************************************************/
+history_t       * history_New                        ();
+vlc_bool_t        history_GoBackSavingCurrentItem    ( history_t *,
+                                                       history_item_t * );
+vlc_bool_t        history_GoForwardSavingCurrentItem ( history_t *,
+                                                       history_item_t * );
+vlc_bool_t        history_CanGoBack                  ( history_t * );
+vlc_bool_t        history_CanGoForward               ( history_t * );
+history_item_t  * history_Item                       ( history_t * );
+void              history_Prune                      ( history_t * );
+void              history_PruneAndInsert             ( history_t *,
+                                                       history_item_t * );
+unsigned int      history_Count                      ( history_t * );
+unsigned int      history_Index                      ( history_t * );
+
+history_item_t  * historyItem_New                    ( char *, char * );
+
+#endif /* __VLC_HISTORY_H__ */
+
diff --git a/modules/codec/cmml/intf.c b/modules/codec/cmml/intf.c
new file mode 100644 (file)
index 0000000..1907938
--- /dev/null
@@ -0,0 +1,835 @@
+/*****************************************************************************
+ * intf.c: interface for CMML annotations/hyperlinks
+ *****************************************************************************
+ * Copyright (C) 2003-2004 Commonwealth Scientific and Industrial Research
+ *                         Organisation (CSIRO) Australia
+ * Copyright (C) 2004 VideoLAN
+ *
+ * $Id$
+ *
+ * Authors: Andre Pang <Andre.Pang@csiro.au>
+ *
+ * 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., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
+ *****************************************************************************/
+
+/*****************************************************************************
+ * Preamble
+ *****************************************************************************/
+#include <stdlib.h>                                      /* malloc(), free() */
+#include <string.h>
+#include <unistd.h>
+
+#include <vlc/vlc.h>
+#include <vlc/decoder.h>
+#include <vlc/intf.h>
+#include <vlc/vout.h>
+
+#include <osd.h>
+
+#include "stream_control.h"
+#include "input_ext-intf.h"
+#include "input_ext-dec.h"
+
+#include "vlc_keys.h"
+
+#include "browser_open.h"
+#include "history.h"
+#include "xstrcat.h"
+#include "xurl.h"
+
+#undef  CMML_INTF_USE_TIMED_URIS
+
+#undef  CMML_INTF_DEBUG
+#undef  CMML_INTF_SUBPICTURE_DEBUG
+#undef  CMML_INTF_HISTORY_DEBUG
+
+/*****************************************************************************
+ * intf_sys_t: description and status of interface
+ *****************************************************************************/
+struct intf_sys_t
+{
+    decoder_t *         p_cmml_decoder;
+    input_thread_t *    p_input;
+
+    vlc_bool_t          b_key_pressed;
+};
+
+struct navigation_history_t
+{
+    int i_history_size;
+    int i_last_item;
+};
+
+/*****************************************************************************
+ * Local prototypes.
+ *****************************************************************************/
+static int   InitThread                 ( intf_thread_t * );
+static int   MouseEvent                 ( vlc_object_t *, char const *,
+                                          vlc_value_t, vlc_value_t, void * );
+static int   KeyEvent                   ( vlc_object_t *, char const *,
+                                          vlc_value_t, vlc_value_t, void * );
+
+static void  FollowAnchor               ( intf_thread_t * );
+static void  GoBack                     ( intf_thread_t * );
+static void  GoForward                  ( intf_thread_t * );
+
+static char *GetTimedURLFromPlaylistItem( intf_thread_t *, playlist_item_t * );
+static char *GetTimedURIFragmentForTime ( int );
+static int   GetCurrentTimeInSeconds    ( input_thread_t * );
+static int   DisplayAnchor              ( intf_thread_t *, vout_thread_t *,
+                                          char *, char * );
+static history_t * GetHistory           ( playlist_t * );
+static void  ReplacePlaylistItem        ( playlist_t *, char * );
+
+/* Exported functions */
+static void RunIntf        ( intf_thread_t *p_intf );
+
+/*****************************************************************************
+ * OpenIntf: initialize CMML interface
+ *****************************************************************************/
+int E_(OpenIntf) ( vlc_object_t *p_this )
+{
+    intf_thread_t *p_intf = (intf_thread_t *)p_this;
+
+    p_intf->p_sys = malloc( sizeof( intf_sys_t ) );
+    if( p_intf->p_sys == NULL )
+    {
+        return( 1 );
+    };
+
+    p_intf->pf_run = RunIntf;
+    
+    var_AddCallback( p_intf->p_vlc, "key-pressed", KeyEvent, p_intf );
+    /* we also need to add the callback for "mouse-clicked", but do that later
+     * when we've found a p_vout */
+
+    return( 0 );
+}
+
+/*****************************************************************************
+ * CloseIntf: destroy dummy interface
+ *****************************************************************************/
+void E_(CloseIntf) ( vlc_object_t *p_this )
+{
+    intf_thread_t * p_intf = (intf_thread_t *)p_this;
+    vout_thread_t * p_vout;
+
+#ifdef CMML_INTF_DEBUG
+    msg_Dbg( p_intf, "freeing CMML interface" );
+#endif
+
+    /* Erase the anchor text description from the video output if it exists */
+    p_vout = vlc_object_find( p_intf, VLC_OBJECT_VOUT, FIND_ANYWHERE );
+    if( p_vout != NULL && p_vout->p_subpicture != NULL )
+    {
+        subpicture_t *p_subpic;
+        int          i_subpic;
+
+        for( i_subpic = 0; i_subpic < VOUT_MAX_SUBPICTURES; i_subpic++ )
+        {
+            p_subpic = &p_vout->p_subpicture[i_subpic];
+
+            if( p_subpic != NULL &&
+              ( p_subpic->i_status == RESERVED_SUBPICTURE
+                || p_subpic->i_status == READY_SUBPICTURE ) )
+            {
+                vout_DestroySubPicture( p_vout, p_subpic );
+            }
+        }
+    }
+    if( p_vout ) vlc_object_release( p_vout );
+
+    var_DelCallback( p_intf->p_vlc, "key-pressed", KeyEvent, p_intf );
+
+    vlc_object_release( p_intf->p_sys->p_cmml_decoder );
+   
+    free( p_intf->p_sys );
+}
+
+
+/*****************************************************************************
+ * RunIntf: main loop
+ *****************************************************************************/
+static void RunIntf( intf_thread_t *p_intf )
+{
+    vout_thread_t * p_vout = NULL;
+
+    if( InitThread( p_intf ) < 0 )
+    {
+        msg_Err( p_intf, "can't initialize CMML interface" );
+        return;
+    }
+#ifdef CMML_INTF_DEBUG
+    msg_Dbg( p_intf, "CMML intf initialized" );
+#endif
+
+    /* if video output is dying, disassociate ourselves from it */
+    if( p_vout && p_vout->b_die )
+    {
+        var_DelCallback( p_vout, "mouse-clicked", MouseEvent, p_intf );
+        vlc_object_release( p_vout );
+        p_vout = NULL;
+    }
+
+    /* Main loop */
+    while( !p_intf->b_die )
+    {
+        vlc_value_t val;
+        decoder_t *p_cmml_decoder;
+        
+        /* find a video output if we currently don't have one */
+        if( p_vout == NULL )
+        {
+            p_vout = vlc_object_find( p_intf->p_sys->p_input,
+                                      VLC_OBJECT_VOUT, FIND_CHILD );
+            if( p_vout )
+            {
+#ifdef CMML_INTF_DEBUG
+                msg_Dbg( p_intf, "found vout thread" );
+#endif
+                var_AddCallback( p_vout, "mouse-clicked", MouseEvent, p_intf );
+            }
+        }
+
+        vlc_mutex_lock( &p_intf->change_lock );
+
+        /*
+         * keyboard event
+         */
+        if( p_intf->p_sys->b_key_pressed )
+        {
+            vlc_value_t val;
+            int i, i_action = -1;
+            struct hotkey *p_hotkeys = p_intf->p_vlc->p_hotkeys;
+
+            /* Find action triggered by hotkey (if any) */
+            var_Get( p_intf->p_vlc, "key-pressed", &val );
+
+            /* Acknowledge that we've handled the b_key_pressed event */
+            p_intf->p_sys->b_key_pressed = VLC_FALSE;
+
+#ifdef CMML_INTF_DEBUG
+            msg_Dbg( p_intf, "Got a keypress: %d", val.i_int );
+#endif
+            
+            for( i = 0; p_hotkeys[i].psz_action != NULL; i++ )
+            {
+                if( p_hotkeys[i].i_key == val.i_int )
+                    i_action = p_hotkeys[i].i_action;
+            }
+
+            /* What did the user do? */
+            if( i_action != -1 )
+            {
+                switch( i_action )
+                {
+                    case ACTIONID_NAV_ACTIVATE:
+                        FollowAnchor( p_intf );
+                        break;
+                    case ACTIONID_HISTORY_BACK:
+                        GoBack( p_intf );
+                        break;
+                    case ACTIONID_HISTORY_FORWARD:
+                        GoForward( p_intf );
+                        break;
+                    default:
+                        break;
+                }
+            }
+        }       
+
+        vlc_mutex_unlock( &p_intf->change_lock );
+
+        /*
+         * Get a pending anchor description/URL from the CMML decoder
+         * and display it on screen
+         */
+        p_cmml_decoder = p_intf->p_sys->p_cmml_decoder;
+        if( var_Get( p_cmml_decoder, "psz-current-anchor-description", &val )
+                == VLC_SUCCESS )
+        {
+            if( val.p_address )
+            {
+                char *psz_description = NULL;
+                char *psz_url = NULL;
+
+                psz_description = val.p_address;
+
+                if( var_Get( p_cmml_decoder, "psz-current-anchor-url", &val )
+                        == VLC_SUCCESS )
+                {
+                    psz_url = val.p_address;
+                }
+
+                if( p_vout != NULL )
+                {
+                    if( DisplayAnchor( p_intf, p_vout, psz_description,
+                                psz_url ) != VLC_SUCCESS )
+                    {
+                        /* text render unsuccessful: do nothing */
+                    }
+                    else
+                    {
+                        /* text render successful: clear description */
+                        val.p_address = NULL;
+                        if( var_Set( p_cmml_decoder,
+                                    "psz-current-anchor-description", val ) !=
+                                VLC_SUCCESS )
+                        {
+                            msg_Dbg( p_intf, "reset of "
+                                    "psz-current-anchor-description failed" );
+                        }
+                        free( psz_description );
+                        psz_url = NULL;
+                    }
+                }
+            }
+        }
+
+        /* Wait a bit */
+        msleep( INTF_IDLE_SLEEP );
+    }
+
+    /* if we're here, the video output is dying: release the vout object */
+
+    if( p_vout )
+    {
+        var_DelCallback( p_vout, "mouse-clicked", MouseEvent, p_intf );
+        vlc_object_release( p_vout );
+    }
+
+    vlc_object_release( p_intf->p_sys->p_input );
+}
+
+/*****************************************************************************
+ * InitThread:
+ *****************************************************************************/
+static int InitThread( intf_thread_t * p_intf )
+{
+    /* We might need some locking here */
+    if( !p_intf->b_die )
+    {
+        input_thread_t * p_input;
+        decoder_t *p_cmml_decoder;
+
+        p_cmml_decoder = vlc_object_find( p_intf, VLC_OBJECT_DECODER, FIND_PARENT );
+        p_input = vlc_object_find( p_intf, VLC_OBJECT_INPUT, FIND_PARENT );
+
+#ifdef CMML_INTF_DEBUG
+        msg_Dbg( p_intf, "cmml decoder at %p, input thread at %p",
+                 p_cmml_decoder, p_input );
+#endif
+
+        /* Maybe the input just died */
+        if( p_input == NULL )
+        {
+            return VLC_EGENERIC;
+        }
+
+        vlc_mutex_lock( &p_intf->change_lock );
+
+        p_intf->p_sys->p_input = p_input;
+        p_intf->p_sys->p_cmml_decoder = p_cmml_decoder;
+
+        p_intf->p_sys->b_key_pressed = VLC_FALSE;
+
+        vlc_mutex_unlock( &p_intf->change_lock );
+
+        return VLC_SUCCESS;
+    }
+    else
+    {
+        return VLC_EGENERIC;
+    }
+}
+
+/*****************************************************************************
+ * MouseEvent: callback for mouse events
+ *****************************************************************************/
+static int MouseEvent( vlc_object_t *p_this, char const *psz_var,
+                       vlc_value_t oldval, vlc_value_t newval, void *p_data )
+{
+    /* TODO: handle mouse clicks on the anchor text */
+
+    return VLC_SUCCESS;
+}
+
+/*****************************************************************************
+ * KeyEvent: callback for keyboard events
+ *****************************************************************************/
+static int KeyEvent( vlc_object_t *p_this, char const *psz_var,
+                       vlc_value_t oldval, vlc_value_t newval, void *p_data )
+{
+    intf_thread_t *p_intf = (intf_thread_t *)p_data;
+    vlc_mutex_lock( &p_intf->change_lock );
+
+    p_intf->p_sys->b_key_pressed = VLC_TRUE;
+    
+    vlc_mutex_unlock( &p_intf->change_lock );
+
+    return VLC_SUCCESS;
+}
+
+/*****************************************************************************
+ * FollowAnchor: follow the current anchor being displayed to the user
+ *****************************************************************************/
+static void FollowAnchor ( intf_thread_t *p_intf )
+{
+    intf_sys_t *p_sys;
+    decoder_t *p_cmml_decoder;
+    char *psz_url = NULL;
+    vlc_value_t val;
+
+    msg_Dbg( p_intf, "User followed anchor" );
+
+    p_sys = p_intf->p_sys;
+    p_cmml_decoder = p_sys->p_cmml_decoder;
+
+    if( var_Get( p_cmml_decoder, "psz-current-anchor-url", &val ) ==
+            VLC_SUCCESS )
+    {
+        if( val.p_address ) psz_url = val.p_address;
+    }
+
+#ifdef CMML_INTF_DEBUG
+    msg_Dbg( p_intf, "Current URL is \"%s\"", psz_url );
+#endif
+
+    if( psz_url )
+    {
+        playlist_t *p_playlist;
+        playlist_item_t *p_current_item;
+        char *psz_uri_to_load;
+
+        p_playlist = (playlist_t *) vlc_object_find( p_intf, 
+                VLC_OBJECT_PLAYLIST, FIND_ANYWHERE );
+        if ( !p_playlist )
+        {
+            msg_Warn( p_intf, "can't find playlist" );
+            return;
+        }
+
+        /* Get new URL */
+        p_current_item = p_playlist->pp_items[p_playlist->i_index];
+#ifdef CMML_INTF_DEBUG
+        msg_Dbg( p_intf, "Current playlist item URL is \"%s\"",
+                p_current_item->psz_uri );
+#endif
+
+        psz_uri_to_load = XURL_Concat( p_current_item->input.psz_uri,
+                                       psz_url );
+
+#ifdef CMML_INTF_DEBUG
+        msg_Dbg( p_intf, "URL to load is \"%s\"", psz_uri_to_load );
+#endif
+
+        mtime_t i_seconds;
+        vlc_value_t time;
+        if( var_Get( p_intf->p_sys->p_input, "time", &time ) )
+        {
+            msg_Dbg( p_intf, "couldn't get time from current clip" );
+            time.i_time = 0;
+        }
+        i_seconds = time.i_time / 1000000;
+#ifdef CMML_INTF_DEBUG
+        msg_Dbg( p_intf, "Current time is \"%lld\"", i_seconds );
+#endif
+
+        /* TODO: we need a (much) more robust way of detecting whether
+         * the file's a media file ... */
+        if( strstr( psz_uri_to_load, ".anx" ) != NULL )
+        {
+            history_t *p_history = NULL;
+            history_item_t *p_history_item = NULL;
+            char *psz_timed_url;
+            
+            p_history = GetHistory( p_playlist );
+
+            /* create history item */
+            psz_timed_url = GetTimedURLFromPlaylistItem( p_intf, p_current_item );
+            p_history_item = historyItem_New( psz_timed_url, psz_timed_url );
+            free( psz_timed_url );
+
+            if( !p_history_item )
+            {
+                msg_Warn( p_intf, "could not initialise history item" );
+            }
+            else
+            {
+#ifdef CMML_INTF_DEBUG
+                msg_Dbg( p_intf, "history pre-index %d", p_history->i_index );
+#endif
+                history_PruneAndInsert( p_history, p_history_item );
+#ifdef CMML_INTF_DEBUG
+                msg_Dbg( p_intf, "new history item at %p, uri is \"%s\"",
+                         p_history_item, p_history_item->psz_uri );
+                msg_Dbg( p_intf, "history index now %d", p_history->i_index );
+#endif
+            }
+
+            /* free current-anchor-url */
+            free( psz_url );
+            val.p_address = NULL;
+            if( var_Set( p_cmml_decoder, "psz-current-anchor-url", val ) !=
+                    VLC_SUCCESS )
+            {
+                msg_Dbg( p_intf, "couldn't reset psz-current-anchor-url" );
+            }
+
+            ReplacePlaylistItem( p_playlist, psz_uri_to_load );
+        }
+        else
+        {
+            (void) browser_Open( psz_url );
+            playlist_Command( p_playlist, PLAYLIST_PAUSE, 0 );
+        }
+
+        free( psz_uri_to_load );
+
+        vlc_object_release( p_playlist );
+    }
+}
+
+static
+char *GetTimedURLFromPlaylistItem( intf_thread_t *p_intf,
+        playlist_item_t *p_current_item )
+{
+#ifdef CMML_INTF_USE_TIMED_URIS
+    char *psz_url = NULL;
+    char *psz_return_value = NULL;
+    char *psz_seconds = NULL;
+    int i_seconds;
+    
+    psz_url = XURL_GetWithoutFragment( p_current_item->input->psz_uri );
+
+    /* Get current time as a string */
+    if( XURL_IsFileURL( psz_url ) == VLC_TRUE )
+        psz_url = xstrcat( psz_url, "#" );
+    else
+        psz_url = xstrcat( psz_url, "?" );
+
+    /* jump back to 2 seconds before where we are now */
+    i_seconds = GetCurrentTimeInSeconds( p_intf->p_sys->p_input ) - 2;
+    psz_seconds = GetTimedURIFragmentForTime( i_seconds < 0 ? 0 : i_seconds );
+    if( psz_seconds )
+    {
+        psz_url = xstrcat( psz_url, psz_seconds );
+        free( psz_seconds );
+        psz_return_value = psz_url;
+    }
+
+    return psz_return_value;
+#else
+    void *p;
+
+    /* Suppress warning messages about unused functions */
+    p = GetTimedURIFragmentForTime; /* unused */
+    p = GetCurrentTimeInSeconds;    /* unused */
+
+    return strdup( p_current_item->input.psz_uri );
+#endif
+}
+
+
+/*
+ * Get the current time, rounded down to the nearest second
+ *
+ * http://www.ietf.org/internet-drafts/draft-pfeiffer-temporal-fragments-02.txt
+ */
+static
+int GetCurrentTimeInSeconds( input_thread_t *p_input )
+{
+    vlc_value_t time;
+    mtime_t i_seconds;
+
+    var_Get( p_input, "time", &time );
+    i_seconds = time.i_time / 1000000;
+
+    return i_seconds;
+}
+
+static
+char *GetTimedURIFragmentForTime( int seconds )
+{
+    char *psz_time;
+
+    asprintf( &psz_time, "%d", seconds );
+
+    return psz_time;
+}
+
+static
+void GoBack( intf_thread_t *p_intf )
+{
+    vlc_value_t history;
+    history_t *p_history = NULL;
+    history_item_t *p_history_item = NULL;
+    history_item_t *p_new_history_item = NULL;
+    playlist_t *p_playlist = NULL;
+    char *psz_timed_url = NULL;
+
+#ifdef CMML_INTF_DEBUG
+    msg_Dbg( p_intf, "Going back in navigation history" );
+#endif
+
+    /* Find the playlist */
+    p_playlist = (playlist_t *) vlc_object_find( p_intf, 
+            VLC_OBJECT_PLAYLIST, FIND_ANYWHERE );
+    if ( !p_playlist )
+    {
+        msg_Warn( p_intf, "can't find playlist" );
+        return;
+    }
+
+    /* Retrieve navigation history from playlist */
+    if( var_Get( p_playlist, "navigation-history", &history ) != VLC_SUCCESS ||
+        !history.p_address )
+    {
+        /* History doesn't exist yet: ignore user's request */
+        msg_Warn( p_intf, "can't go back: no history exists yet" );
+        vlc_object_release( p_playlist );
+        return;
+    }
+
+    p_history = history.p_address;
+#ifdef CMML_INTF_DEBUG
+    msg_Dbg( p_intf, "back: nav history retrieved from %p", p_history );
+    msg_Dbg( p_intf, "nav history index:%d, p_xarray:%p", p_history->i_index,
+             p_history->p_xarray );
+#endif
+
+    /* Check whether we can go back in the history */
+    if( history_CanGoBack( p_history ) == VLC_FALSE )
+    {
+        msg_Warn( p_intf, "can't go back: already at beginning of history" );
+        vlc_object_release( p_playlist );
+        return;
+    }
+
+    playlist_item_t *p_current_item;
+    p_current_item = p_playlist->pp_items[p_playlist->i_index];
+
+    /* Save the currently-playing media in a new history item */
+    psz_timed_url = GetTimedURLFromPlaylistItem( p_intf, p_current_item );
+    p_new_history_item = historyItem_New( psz_timed_url, psz_timed_url );
+    free( psz_timed_url );
+
+    if( !p_new_history_item )
+    {
+#ifdef CMML_INTF_DEBUG
+        msg_Dbg( p_intf, "back: could not initialise new history item" );
+#endif
+        vlc_object_release( p_playlist );
+        return;
+    }
+
+    /* Go back in the history, saving the currently-playing item */
+    (void) history_GoBackSavingCurrentItem( p_history, p_new_history_item );
+    p_history_item = history_Item( p_history );
+
+#ifdef CMML_INTF_DEBUG
+    msg_Dbg( p_intf, "retrieving item from h index %d", p_history->i_index );
+    msg_Dbg( p_intf, "got previous history item: %p", p_history_item );
+    msg_Dbg( p_intf, "prev history item URL: \"%s\"", p_history_item->psz_uri );
+#endif
+
+    ReplacePlaylistItem( p_playlist, p_history_item->psz_uri );
+    vlc_object_release( p_playlist );
+}
+
+static
+void GoForward( intf_thread_t *p_intf )
+{
+    vlc_value_t history;
+    history_t *p_history = NULL;
+    history_item_t *p_history_item = NULL;
+    history_item_t *p_new_history_item = NULL;
+    playlist_t *p_playlist = NULL;
+
+#ifdef CMML_INTF_DEBUG
+    msg_Dbg( p_intf, "Going forward in navigation history" );
+#endif
+
+    /* Find the playlist */
+    p_playlist = (playlist_t *) vlc_object_find( p_intf, 
+            VLC_OBJECT_PLAYLIST, FIND_ANYWHERE );
+    if ( !p_playlist )
+    {
+        msg_Warn( p_intf, "can't find playlist" );
+        return;
+    }
+
+    /* Retrieve navigation history from playlist */
+    if( var_Get( p_playlist, "navigation-history", &history ) != VLC_SUCCESS ||
+        !history.p_address )
+    {
+        /* History doesn't exist yet: ignore user's request */
+        msg_Warn( p_intf, "can't go back: no history exists yet" );
+        vlc_object_release( p_playlist );
+        return;
+    }
+
+    p_history = history.p_address;
+#ifdef CMML_INTF_DEBUG
+    msg_Dbg( p_intf, "forward: nav history retrieved from %p", p_history );
+    msg_Dbg( p_intf, "nav history index:%d, p_xarray:%p", p_history->i_index,
+             p_history->p_xarray );
+#endif
+
+    /* Check whether we can go forward in the history */
+    if( history_CanGoForward( p_history ) == VLC_FALSE )
+    {
+        msg_Warn( p_intf, "can't go forward: already at end of history" );
+        vlc_object_release( p_playlist );
+        return;
+    }
+
+    /* Save the currently-playing media in a new history item */
+    p_new_history_item = malloc( sizeof(history_item_t) );
+    if( !p_new_history_item )
+    {
+#ifdef CMML_INTF_DEBUG
+        msg_Dbg( p_intf, "forward: could not initialise new history item" );
+#endif
+        vlc_object_release( p_playlist );
+        return;
+    }
+    playlist_item_t *p_current_item;
+    p_current_item = p_playlist->pp_items[p_playlist->i_index];
+    p_new_history_item->psz_uri = GetTimedURLFromPlaylistItem( p_intf, 
+            p_current_item );
+    p_new_history_item->psz_name = p_new_history_item->psz_uri;
+
+    /* Go forward in the history, saving the currently-playing item */
+    (void) history_GoForwardSavingCurrentItem( p_history, p_new_history_item );
+    p_history_item = history_Item( p_history );
+
+#ifdef CMML_INTF_DEBUG
+    msg_Dbg( p_intf, "retrieving item from h index %d", p_history->i_index );
+    msg_Dbg( p_intf, "got next history item: %p", p_history_item );
+    msg_Dbg( p_intf, "next history item URL: \"%s\"", p_history_item->psz_uri );
+#endif
+
+    ReplacePlaylistItem( p_playlist, p_history_item->psz_uri );
+    vlc_object_release( p_playlist );
+}
+
+static void ReplacePlaylistItem( playlist_t *p_playlist, char *psz_uri )
+{
+    playlist_Stop( p_playlist );
+    (void) playlist_Add( p_playlist, psz_uri, psz_uri,
+                         PLAYLIST_REPLACE, p_playlist->i_index );
+    playlist_Goto( p_playlist, p_playlist->i_index );
+}
+
+/****************************************************************************
+ * DisplayAnchor: displays an anchor on the given video output
+ ****************************************************************************/
+static int DisplayAnchor( intf_thread_t *p_intf,
+        vout_thread_t *p_vout,
+        char *psz_anchor_description,
+        char *psz_anchor_url )
+{
+    int i_margin_h, i_margin_v;
+    mtime_t i_now;
+
+    i_margin_h = 0;
+    i_margin_v = 10;
+
+    i_now = mdate();
+
+    if( p_vout )
+    {
+        text_style_t *p_style = NULL;
+
+        text_style_t blue_with_underline = default_text_style;
+        blue_with_underline.b_underline = VLC_TRUE;
+        blue_with_underline.i_color = 0x22ff22;
+
+        if( psz_anchor_url )
+        {
+            /* Should display subtitle underlined and in blue,
+             * but it looks like VLC doesn't implement any
+             * text styles yet.  D'oh! */
+            p_style = &blue_with_underline;
+
+        }
+
+        /* TODO: p_subpicture doesn't have the proper i_x and i_y
+         * coordinates.  Need to look at the subpicture display system to
+         * work out why. */
+        if ( vout_ShowTextAbsolute( p_vout,
+                psz_anchor_description, p_style, OSD_ALIGN_BOTTOM, 
+                i_margin_h, i_margin_v, i_now, 0 ) == VLC_SUCCESS )
+        {
+            /* Displayed successfully */
+#ifdef CMML_INTF_SUBPICTURE_DEBUG
+            msg_Dbg( p_intf, "subpicture created at (%d, %d) (%d, %d)",
+                     p_subpicture->i_x, p_subpicture->i_y,
+                     p_subpicture->i_width, p_subpicture->i_height );
+#endif
+        }
+        else
+        {
+            return VLC_EGENERIC;
+        }
+
+    }
+    else
+    {
+        msg_Dbg( p_intf, "DisplayAnchor couldn't find a video output" );
+        return VLC_EGENERIC;
+    }
+
+    return VLC_SUCCESS;
+}
+
+static history_t * GetHistory( playlist_t *p_playlist )
+{
+    vlc_value_t val;
+    history_t *p_history = NULL;
+
+    if( var_Get( p_playlist, "navigation-history", &val ) != VLC_SUCCESS )
+    {
+        /* history doesn't exist yet: need to create it */
+        history_t *new_history = history_New();
+        val.p_address = new_history;
+        var_Create( p_playlist, "navigation-history",
+                VLC_VAR_ADDRESS|VLC_VAR_DOINHERIT );
+        if( var_Set( p_playlist, "navigation-history", val ) != VLC_SUCCESS )
+        {
+            msg_Warn( p_playlist, "could not initialise history" );
+        }
+        else
+        {
+            p_history = new_history;
+#ifdef CMML_INTF_HISTORY_DEBUG
+            msg_Dbg( p_playlist, "nav history created at %p", new_history );
+            msg_Dbg( p_playlist, "nav history index:%d, p_xarray:%p",
+                     p_history->i_index, p_history->p_xarray );
+#endif
+        }
+    }
+    else
+    {
+        p_history = val.p_address;
+#ifdef CMML_INTF_HISTORY_DEBUG
+        msg_Dbg( p_playlist, "nav history retrieved from %p", p_history );
+#endif
+    }
+
+    return p_history;
+}
+
diff --git a/modules/codec/cmml/xarray.c b/modules/codec/cmml/xarray.c
new file mode 100644 (file)
index 0000000..e0383a7
--- /dev/null
@@ -0,0 +1,249 @@
+/*************************************************************************
+ * xarray.c: Mutable (dynamically growable) array
+ *************************************************************************
+ * Copyright (C) 2004 Commonwealth Scientific and Industrial Research
+ *                    Organisation (CSIRO) Australia
+ * Copyright (C) 2004 VideoLAN
+ *
+ * $Id$
+ *
+ * Authors: Andre Pang <Andre.Pang@csiro.au>
+ *
+ * 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., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
+ ************************************************************************/
+
+#include <stdlib.h>
+#include <string.h> /* memmove(1) */
+
+#include "xarray.h"
+
+#define XARRAY_ASSERT_NOT_NULL(xarray) \
+    { \
+        if (xarray == NULL) return XARRAY_ENULLPOINTER; \
+    }
+
+#define XARRAY_BOUNDS_CHECK(xarray, index) \
+    { \
+        if (index < 0) \
+            return XARRAY_ENEGATIVEINDEX; \
+        else if (xarray->last_valid_element != -1 && \
+                 (int) index > xarray->last_valid_element) \
+            return XARRAY_EINDEXTOOLARGE; \
+    }
+
+#define XARRAY_GROW_ARRAY(xarray) \
+    { \
+        xarray->array = (void *) realloc (xarray->array, xarray->size * 2); \
+        if (xarray->array == NULL) return XARRAY_ENOMEM; \
+    }
+
+XSTATIC XArray * xarray_New (unsigned int initial_size_hint)
+{
+    XArray *new_xarray = NULL;
+    void *inner_array;
+    unsigned int initial_size;
+
+    new_xarray = (XArray *) malloc (sizeof(XArray));
+    if (new_xarray == NULL) return NULL;
+
+    if (initial_size_hint <= 0)
+        initial_size = XARRAY_DEFAULT_SIZE;
+    else
+        initial_size = initial_size_hint;
+
+    inner_array = calloc (initial_size, sizeof(void *));
+
+    new_xarray->last_valid_element = -1;
+    new_xarray->size = initial_size;
+    new_xarray->last_error = 0;
+
+    if (inner_array == NULL)
+    {
+        free (new_xarray);
+        return NULL;
+    }
+
+    new_xarray->array = inner_array;
+
+    /* Make a dummy reference to other functions, so that we don't get
+     * warnings about unused functions from the compiler.  Ahem :) */
+    while (0)
+    {
+        void *dummy_reference;
+
+        dummy_reference = xarray_AddObject;
+        dummy_reference = xarray_InsertObject;
+        dummy_reference = xarray_RemoveLastObject;
+        dummy_reference = xarray_RemoveObject;
+        dummy_reference = xarray_RemoveObjects;
+        dummy_reference = xarray_RemoveObjectsAfter;
+        dummy_reference = xarray_ReplaceObject;
+
+        dummy_reference = xarray_ObjectAtIndex;
+        dummy_reference = xarray_Count;
+    }
+    
+    return new_xarray;
+}
+
+XSTATIC int xarray_ObjectAtIndex (XArray *xarray, unsigned int index,
+        void **out_object)
+{
+    XARRAY_ASSERT_NOT_NULL (xarray);
+    XARRAY_BOUNDS_CHECK (xarray, index);
+
+    *out_object = xarray->array[index];
+
+    return XARRAY_SUCCESS;
+}
+
+XSTATIC int xarray_AddObject (XArray *xarray, void *object)
+{
+    XARRAY_ASSERT_NOT_NULL (xarray);
+
+    ++xarray->last_valid_element;
+    if (xarray->last_valid_element >= (int) xarray->size)
+    {
+        XARRAY_GROW_ARRAY (xarray);
+    }
+
+    xarray->array[xarray->last_valid_element] = object;
+
+    return XARRAY_SUCCESS;
+}
+
+XSTATIC int xarray_InsertObject (XArray *xarray, void *object,
+        unsigned int at_index)
+{
+    XARRAY_ASSERT_NOT_NULL (xarray);
+    ++xarray->last_valid_element;
+    XARRAY_BOUNDS_CHECK (xarray, at_index);
+    if (xarray->last_valid_element >= (int) xarray->size)
+    {
+        XARRAY_GROW_ARRAY (xarray);
+    }
+
+    /* Shift everything from a[i] onward one pointer forward */
+
+    if ((int) at_index < xarray->last_valid_element)
+    {
+        (void) memmove (&xarray->array[at_index + 1],
+                        &xarray->array[at_index],
+                        (xarray->last_valid_element - at_index) *
+                            sizeof(void *));
+    }
+
+    xarray->array[at_index] = object;
+
+    return XARRAY_SUCCESS;
+}
+
+XSTATIC int xarray_RemoveLastObject (XArray *xarray)
+{
+    XARRAY_ASSERT_NOT_NULL (xarray);
+
+    if (xarray->last_valid_element == -1)
+        return XARRAY_EEMPTYARRAY;
+
+    xarray->array[xarray->last_valid_element] = NULL;
+    --xarray->last_valid_element;
+
+    return XARRAY_SUCCESS;
+}
+
+XSTATIC int xarray_RemoveObject (XArray *xarray, unsigned int at_index)
+{
+    XARRAY_ASSERT_NOT_NULL (xarray);
+    XARRAY_BOUNDS_CHECK (xarray, at_index);
+
+    /* Shift everything from a[i] onward one pointer backward */
+
+    if ((int) at_index < xarray->last_valid_element)
+    {
+        (void) memmove (&xarray->array[at_index],
+                        &xarray->array[at_index + 1],
+                        (xarray->last_valid_element - at_index) *
+                            sizeof(void *));
+    }
+
+    xarray->array[xarray->last_valid_element] = NULL;
+    --xarray->last_valid_element;
+
+    return XARRAY_SUCCESS;    
+}
+
+XSTATIC int xarray_RemoveObjects (XArray *xarray, unsigned int at_index,
+        int count)
+{
+    int i;
+
+    XARRAY_ASSERT_NOT_NULL (xarray);
+    XARRAY_BOUNDS_CHECK (xarray, at_index);
+
+    if (count == 0) return XARRAY_SUCCESS;
+
+    if ((int) at_index + (count - 1) > xarray->last_valid_element)
+        return XARRAY_ECOUNTOUTOFBOUNDS;
+
+    for (i = 0; i < count; i++)
+    {
+        int e = xarray_RemoveObject (xarray, at_index);
+        if (e != XARRAY_SUCCESS) return e;
+    }
+
+    return XARRAY_SUCCESS;
+}
+
+XSTATIC int xarray_RemoveObjectsAfter (XArray *xarray, unsigned int index)
+{
+    XARRAY_ASSERT_NOT_NULL (xarray);
+    XARRAY_BOUNDS_CHECK (xarray, index);
+
+    index++;
+
+    while ((int) index <= xarray->last_valid_element)
+    {
+        int e = xarray_RemoveObject (xarray, index);
+        if (e != XARRAY_SUCCESS) return e;
+    }
+
+    return XARRAY_SUCCESS;
+}
+
+XSTATIC int xarray_ReplaceObject (XArray *xarray, unsigned int index,
+        void *new_object)
+{
+    XARRAY_ASSERT_NOT_NULL (xarray);
+    XARRAY_BOUNDS_CHECK (xarray, index);
+
+    xarray->array[index] = new_object;
+
+    return XARRAY_SUCCESS;
+}
+
+XSTATIC int xarray_Count (XArray *xarray, unsigned int *out_count)
+{
+    XARRAY_ASSERT_NOT_NULL (xarray);
+
+    *out_count = xarray->last_valid_element + 1;
+
+    return XARRAY_SUCCESS;
+}
+
+
+#undef XARRAY_ASSERT_NOT_NULL
+#undef XARRAY_BOUNDS_CHECK
+#undef XARRAY_GROW_ARRAY
+
diff --git a/modules/codec/cmml/xarray.h b/modules/codec/cmml/xarray.h
new file mode 100644 (file)
index 0000000..141b40f
--- /dev/null
@@ -0,0 +1,73 @@
+/*************************************************************************
+ * xarray.h: Mutable (dynamically growable) array (header file)
+ *************************************************************************
+ * Copyright (C) 2004 Commonwealth Scientific and Industrial Research
+ *                    Organisation (CSIRO) Australia
+ * Copyright (C) 2004 VideoLAN
+ *
+ * $Id$
+ *
+ * Authors: Andre Pang <Andre.Pang@csiro.au>
+ *
+ * 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., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
+ ************************************************************************/
+
+#ifndef __XARRAY_H__
+#define __XARRAY_H__
+
+/* define this to 'static' for static linkage */
+#define XSTATIC
+
+#define XARRAY_DEFAULT_SIZE 69
+#define xarray_malloc malloc
+
+/* Error codes */
+enum xarray_errors
+{
+    XARRAY_SUCCESS, XARRAY_ENULLPOINTER, XARRAY_ENEGATIVEINDEX,
+    XARRAY_EINDEXTOOLARGE, XARRAY_ENOMEM, XARRAY_EEMPTYARRAY,
+    XARRAY_ECOUNTOUTOFBOUNDS
+};
+
+
+typedef struct
+{
+    void **array;
+    int last_valid_element;
+    unsigned int size;
+    unsigned int last_error;
+}
+XArray;
+
+/* Mutable methods */
+XSTATIC int      xarray_AddObject (XArray *xarray, void *object);
+XSTATIC int      xarray_InsertObject (XArray *xarray, void *object,
+                                      unsigned int at_index);
+XSTATIC int      xarray_RemoveLastObject (XArray *xarray);
+XSTATIC int      xarray_RemoveObject (XArray *xarray, unsigned int at_index);
+XSTATIC int      xarray_RemoveObjects (XArray *xarray, unsigned int at_index,
+                                       int count);
+XSTATIC int      xarray_RemoveObjectsAfter (XArray *xarray, unsigned int index);
+XSTATIC int      xarray_ReplaceObject (XArray *xarray, unsigned int index,
+                                       void *new_object);
+
+/* Immutable methods */
+XSTATIC XArray * xarray_New ();
+XSTATIC int      xarray_ObjectAtIndex (XArray *xarray, unsigned int index,
+                                       void **out_object);
+XSTATIC int      xarray_Count (XArray *xarray, unsigned int *out_count);
+
+#endif /* __XARRAY_H__ */
+
diff --git a/modules/codec/cmml/xlist.c b/modules/codec/cmml/xlist.c
new file mode 100644 (file)
index 0000000..da02b2e
--- /dev/null
@@ -0,0 +1,245 @@
+/*****************************************************************************
+ * xlist.c : a simple doubly linked list in C
+ *****************************************************************************
+ * Copyright (C) 2003-2004 Commonwealth Scientific and Industrial Research
+ *                         Organisation (CSIRO) Australia
+ * Copyright (C) 2000-2004 VideoLAN
+ *
+ * $Id$
+ *
+ * Authors: Conrad Parker <Conrad.Parker@csiro.au>
+ *          Andre Pang <Andre.Pang@csiro.au>
+ *
+ * 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., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
+ *****************************************************************************/
+
+#include <stdlib.h>
+
+#include "xlist.h"
+
+static XList *
+xlist_node_new (void * data)
+{
+  XList * l;
+
+  l = (XList *) malloc (sizeof (XList));
+  l->prev = l->next = NULL;
+  l->data = data;
+
+  return l;
+}
+
+XList *
+xlist_new (void)
+{
+  return NULL;
+}
+
+XList *
+xlist_clone (XList * list)
+{
+  XList * l, * new_list;
+
+  if (list == NULL) return NULL;
+  new_list = xlist_new ();
+
+  for (l = list; l; l = l->next) {
+    new_list = xlist_append (new_list, l->data);
+  }
+
+  return new_list;
+}
+
+XList *
+xlist_clone_with (XList * list, XCloneFunc clone)
+{
+  XList * l, * new_list;
+  void * new_data;
+
+  if (list == NULL) return NULL;
+  if (clone == NULL) return xlist_clone (list);
+
+  new_list = xlist_new ();
+
+  for (l = list; l; l = l->next) {
+    new_data = clone (l->data);
+    new_list = xlist_append (new_list, new_data);
+  }
+
+  return new_list;
+}
+
+
+XList *
+xlist_tail (XList * list)
+{
+  XList * l;
+  for (l = list; l; l = l->next)
+    if (l->next == NULL) return l;
+  return NULL;
+}
+
+XList *
+xlist_prepend (XList * list, void * data)
+{
+  XList * l = xlist_node_new (data);
+
+  if (list == NULL) return l;
+
+  l->next = list;
+  list->prev = l;
+
+  return l;
+}
+
+XList *
+xlist_append (XList * list, void * data)
+{
+  XList * l = xlist_node_new (data);
+  XList * last;
+
+  if (list == NULL) return l;
+
+  last = xlist_tail (list);
+  if (last) last->next = l;
+  l->prev = last; 
+  return list;
+}
+
+XList *
+xlist_add_before (XList * list, void * data, XList * node)
+{
+  XList * l, * p;
+
+  if (list == NULL) return xlist_node_new (data);
+  if (node == NULL) return xlist_append (list, data);
+  if (node == list) return xlist_prepend (list, data);
+
+  l = xlist_node_new (data);
+  p = node->prev;
+
+  l->prev = p;
+  l->next = node;
+  if (p) p->next = l;
+  node->prev = l;
+  
+  return list;
+}
+
+XList *
+xlist_add_after (XList * list, void * data, XList * node)
+{
+  XList * l, * n;
+
+  if (node == NULL) return xlist_prepend (list, data);
+
+  l = xlist_node_new (data);
+  n = node->next;
+
+  l->prev = node;
+  l->next = n;
+  if (n) n->prev = l;
+  node->next = l;
+
+  return list;
+}
+
+XList *
+xlist_find (XList * list, void * data)
+{
+  XList * l;
+
+  for (l = list; l; l = l->next)
+    if (l->data == data) return l;
+
+  return NULL;
+}
+
+XList *
+xlist_remove (XList * list, XList * node)
+{
+  if (node == NULL) return list;
+
+  if (node->prev) node->prev->next = node->next;
+  if (node->next) node->next->prev = node->prev;
+
+  if (node == list) return list->next;
+  else return list;
+}
+
+int
+xlist_length (XList * list)
+{
+  XList * l;
+  int c = 0;
+
+  for (l = list; l; l = l->next)
+    c++;
+
+  return c;
+}
+
+int
+xlist_is_empty (XList * list)
+{
+  return (list == NULL);
+}
+
+int
+xlist_is_singleton (XList * list)
+{
+  if (list == NULL) return 0;
+  if (list->next == NULL) return 1;
+  else return 0;
+}
+
+/*
+ * xlist_free_with (list, free_func)
+ *
+ * Step through list 'list', freeing each node using free_func(), and
+ * also free the list structure itself.
+ */
+XList *
+xlist_free_with (XList * list, XFreeFunc free_func)
+{
+  XList * l, * ln;
+
+  for (l = list; l; l = ln) {
+    ln = l->next;
+    free_func (l->data);
+    free (l);
+  }
+
+  return NULL;
+}
+
+/*
+ * xlist_free (list)
+ *
+ * Free the list structure 'list', but not its nodes.
+ */
+XList *
+xlist_free (XList * list)
+{
+  XList * l, * ln;
+
+  for (l = list; l; l = ln) {
+    ln = l->next;
+    free (l);
+  }
+
+  return NULL;
+}
+
diff --git a/modules/codec/cmml/xlist.h b/modules/codec/cmml/xlist.h
new file mode 100644 (file)
index 0000000..441345d
--- /dev/null
@@ -0,0 +1,168 @@
+/*****************************************************************************
+ * xlist.h : a simple doubly linked list in C (header file)
+ *****************************************************************************
+ * Copyright (C) 2003-2004 Commonwealth Scientific and Industrial Research
+ *                         Organisation (CSIRO) Australia
+ * Copyright (C) 2000-2004 VideoLAN
+ *
+ * $Id$
+ *
+ * Authors: Conrad Parker <Conrad.Parker@csiro.au>
+ *          Andre Pang <Andre.Pang@csiro.au>
+ *
+ * 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., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
+ *****************************************************************************/
+
+
+#ifndef __XLIST__
+#define __XLIST__
+
+/**
+ * A doubly linked list
+ */
+typedef struct _XList XList;
+
+struct _XList {
+  XList * prev;
+  XList * next;
+  void * data;
+};
+
+/**
+ * Signature of a cloning function.
+ */
+typedef void * (*XCloneFunc) (void * data);
+
+/**
+ * Signature of a freeing function.
+ */
+typedef void * (*XFreeFunc) (void * data);
+
+/** Create a new list
+ * \return a new list
+ */
+XList * xlist_new (void);
+
+/**
+ * Clone a list using the default clone function
+ * \param list the list to clone
+ * \returns a newly cloned list
+ */
+XList * xlist_clone (XList * list);
+
+/**
+ * Clone a list using a custom clone function
+ * \param list the list to clone
+ * \param clone the function to use to clone a list item
+ * \returns a newly cloned list
+ */
+XList * xlist_clone_with (XList * list, XCloneFunc clone);
+
+/**
+ * Return the tail element of a list
+ * \param list the list
+ * \returns the tail element
+ */
+XList * xlist_tail (XList * list);
+
+/**
+ * Prepend a new node to a list containing given data
+ * \param list the list
+ * \param data the data element of the newly created node
+ * \returns the new list head
+ */
+XList * xlist_prepend (XList * list, void * data);
+
+/**
+ * Append a new node to a list containing given data
+ * \param list the list
+ * \param data the data element of the newly created node
+ * \returns the head of the list
+ */
+XList * xlist_append (XList * list, void * data);
+
+/**
+ * Add a new node containing given data before a given node
+ * \param list the list
+ * \param data the data element of the newly created node
+ * \param node the node before which to add the newly created node
+ * \returns the head of the list (which may have changed)
+ */
+XList * xlist_add_before (XList * list, void * data, XList * node);
+
+/**
+ * Add a new node containing given data after a given node
+ * \param list the list
+ * \param data the data element of the newly created node
+ * \param node the node after which to add the newly created node
+ * \returns the head of the list
+ */
+XList * xlist_add_after (XList * list, void * data, XList * node);
+
+/**
+ * Find the first node containing given data in a list
+ * \param list the list
+ * \param data the data element to find
+ * \returns the first node containing given data, or NULL if it is not found
+ */
+XList * xlist_find (XList * list, void * data);
+
+/**
+ * Remove a node from a list
+ * \param list the list
+ * \param node the node to remove
+ * \returns the head of the list (which may have changed)
+ */
+XList * xlist_remove (XList * list, XList * node);
+
+/**
+ * Query the number of items in a list
+ * \param list the list
+ * \returns the number of nodes in the list
+ */
+int xlist_length (XList * list);
+
+/**
+ * Query if a list is empty, ie. contains no items
+ * \param list the list
+ * \returns 1 if the list is empty, 0 otherwise
+ */
+int xlist_is_empty (XList * list);
+
+/**
+ * Query if the list is singleton, ie. contains exactly one item
+ * \param list the list
+ * \returns 1 if the list is singleton, 0 otherwise
+ */
+int xlist_is_singleton (XList * list);
+
+/**
+ * Free a list, using a given function to free each data element
+ * \param list the list
+ * \param free_func a function to free each data element
+ * \returns NULL on success
+ */
+XList * xlist_free_with (XList * list, XFreeFunc free_func);
+
+/**
+ * Free a list, using anx_free() to free each data element
+ * \param list the list
+ * \returns NULL on success
+ */
+XList * xlist_free (XList * list);
+
+#endif /* __XLIST__ */
+
+
diff --git a/modules/codec/cmml/xstrcat.h b/modules/codec/cmml/xstrcat.h
new file mode 100644 (file)
index 0000000..1c9ef6f
--- /dev/null
@@ -0,0 +1,44 @@
+/*****************************************************************************
+ * xstrcat.h: strcat with realloc
+ *****************************************************************************
+ * Copyright (C) 2004 Commonwealth Scientific and Industrial Research
+ *                    Organisation (CSIRO) Australia
+ * Copyright (C) 2004 VideoLAN
+ *
+ * $Id$
+ *
+ * Authors: Andre Pang <Andre.Pang@csiro.au>
+ *
+ * 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., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
+ *****************************************************************************/
+
+#ifndef __XSTRCAT_H__
+#define __XSTRCAT_H__
+
+/* like strcat, but realloc's enough memory for the new string too */
+
+static inline
+char *xstrcat( char *psz_string, char *psz_to_append )
+{
+    size_t i_new_string_length = strlen( psz_string ) +
+        strlen( psz_to_append ) + 1;
+
+    psz_string = (char *) realloc( psz_string, i_new_string_length );
+    
+    return strcat( psz_string, psz_to_append );
+}
+
+#endif /* __XSTRCAT_H__ */
+
diff --git a/modules/codec/cmml/xtag.c b/modules/codec/cmml/xtag.c
new file mode 100644 (file)
index 0000000..a9c3a42
--- /dev/null
@@ -0,0 +1,692 @@
+/*****************************************************************************
+ * xlist.c : a trivial parser for XML-like tags
+ *****************************************************************************
+ * Copyright (C) 2003-2004 Commonwealth Scientific and Industrial Research
+ *                         Organisation (CSIRO) Australia
+ * Copyright (C) 2000-2004 VideoLAN
+ *
+ * $Id$
+ *
+ * Authors: Conrad Parker <Conrad.Parker@csiro.au>
+ *          Andre Pang <Andre.Pang@csiro.au>
+ *
+ * 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., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
+ *****************************************************************************/
+
+#include "config.h"
+
+#include <ctype.h>
+#include <string.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <xlist.h>
+
+#undef XTAG_DEBUG
+
+#undef FALSE
+#undef TRUE
+
+#define FALSE (0)
+#define TRUE (!FALSE)
+
+#undef MIN
+#define MIN(a,b) ((a)<(b)?(a):(b))
+
+#undef MAX
+#define MAX(a,b) ((a)>(b)?(a):(b))
+
+typedef struct _XTag XTag;
+typedef struct _XAttribute XAttribute;
+typedef struct _XTagParser XTagParser;
+
+/*
+ * struct _XTag is kind of a union ... it normally represents a whole
+ * tag (and its children), but it could alternatively represent some
+ * PCDATA. Basically, if tag->pcdata is non-NULL, interpret only it and
+ * ignore the name, attributes and inner_tags.
+ */
+struct _XTag {
+  char * name;
+  char * pcdata;
+  XTag * parent;
+  XList * attributes;
+  XList * children;
+  XList * current_child;
+};
+
+struct _XAttribute {
+  char * name;
+  char * value;
+};
+
+struct _XTagParser {
+  int valid; /* boolean */
+  XTag * current_tag;
+  char * start;
+  char * end;
+};
+
+/* Character classes */
+#define X_NONE           0
+#define X_WHITESPACE  1<<0
+#define X_OPENTAG     1<<1
+#define X_CLOSETAG    1<<2
+#define X_DQUOTE      1<<3
+#define X_SQUOTE      1<<4
+#define X_EQUAL       1<<5
+#define X_SLASH       1<<6
+
+static int
+xtag_cin (char c, int char_class)
+{
+  if (char_class & X_WHITESPACE)
+    if (isspace(c)) return TRUE;
+
+  if (char_class & X_OPENTAG)
+    if (c == '<') return TRUE;
+
+  if (char_class & X_CLOSETAG)
+    if (c == '>') return TRUE;
+
+  if (char_class & X_DQUOTE)
+    if (c == '"') return TRUE;
+
+  if (char_class & X_SQUOTE)
+    if (c == '\'') return TRUE;
+
+  if (char_class & X_EQUAL)
+    if (c == '=') return TRUE;
+
+  if (char_class & X_SLASH)
+    if (c == '/') return TRUE;
+
+  return FALSE;
+}
+
+static int
+xtag_index (XTagParser * parser, int char_class)
+{
+  char * s;
+  int i;
+
+  s = parser->start;
+
+  for (i = 0; s[i] && s != parser->end; i++) {
+    if (xtag_cin(s[i], char_class)) return i;
+  }
+
+  return -1;
+}
+
+static void
+xtag_skip_over (XTagParser * parser, int char_class)
+{
+  char * s;
+  int i;
+
+  if (!parser->valid) return;
+
+  s = (char *)parser->start;
+
+  for (i = 0; s[i] && s != parser->end; i++) {
+    if (!xtag_cin(s[i], char_class)) {
+      parser->start = &s[i];
+      return;
+    }
+  }
+
+  return;
+}
+
+static void
+xtag_skip_whitespace (XTagParser * parser)
+{
+  xtag_skip_over (parser, X_WHITESPACE);
+}
+
+#if 0
+static void
+xtag_skip_to (XTagParser * parser, int char_class)
+{
+  char * s;
+  int i;
+
+  if (!parser->valid) return;
+
+  s = (char *)parser->start;
+
+  for (i = 0; s[i] && s != parser->end; i++) {
+    if (xtag_cin(s[i], char_class)) {
+      parser->start = &s[i];
+      return;
+    }
+  }
+
+  return;  
+}
+#endif
+
+static char *
+xtag_slurp_to (XTagParser * parser, int good_end, int bad_end)
+{
+  char * s, * ret;
+  int xi;
+
+  if (!parser->valid) return NULL;
+
+  s = parser->start;
+
+  xi = xtag_index (parser, good_end | bad_end);
+
+  if (xi > 0 && xtag_cin (s[xi], good_end)) {
+    ret = malloc ((xi+1) * sizeof(char));
+    strncpy (ret, s, xi);
+    ret[xi] = '\0';
+    parser->start = &s[xi];
+    return ret;
+  }
+
+  return NULL;
+}
+
+static int
+xtag_assert_and_pass (XTagParser * parser, int char_class)
+{
+  char * s;
+
+  if (!parser->valid) return FALSE;
+
+  s = parser->start;
+
+  if (!xtag_cin (s[0], char_class)) {
+    parser->valid = FALSE;
+    return FALSE;
+  }
+
+  parser->start = &s[1];
+
+  return TRUE;
+}
+
+static char *
+xtag_slurp_quoted (XTagParser * parser)
+{
+  char * s, * ret;
+  int quote = X_DQUOTE; /* quote char to match on */
+  int xi;
+
+  if (!parser->valid) return NULL;
+
+  xtag_skip_whitespace (parser);
+
+  s = parser->start;
+
+  if (xtag_cin (s[0], X_SQUOTE)) quote = X_SQUOTE;
+
+  if (!xtag_assert_and_pass (parser, quote)) return NULL;
+
+  s = parser->start;
+
+  for (xi = 0; s[xi]; xi++) {
+    if (xtag_cin (s[xi], quote)) {
+      if (!(xi > 1 && s[xi-1] == '\\')) break;
+    }
+  }
+
+  ret = malloc ((xi+1) * sizeof(char));
+  strncpy (ret, s, xi);
+  ret[xi] = '\0';
+  parser->start = &s[xi];
+
+  if (!xtag_assert_and_pass (parser, quote)) return NULL;
+
+  return ret;
+}
+
+static XAttribute *
+xtag_parse_attribute (XTagParser * parser)
+{
+  XAttribute * attr;
+  char * name, * value;
+  char * s;
+
+  if (!parser->valid) return NULL;
+
+  xtag_skip_whitespace (parser);
+  name = xtag_slurp_to (parser, X_WHITESPACE | X_EQUAL, X_SLASH | X_CLOSETAG);
+
+  if (name == NULL) return NULL;
+
+  xtag_skip_whitespace (parser);
+  s = parser->start;
+
+  if (!xtag_assert_and_pass (parser, X_EQUAL)) {
+#ifdef XTAG_DEBUG
+    printf ("xtag: attr failed EQUAL on <%s>\n", name);
+#endif
+    goto err_free_name;
+  }
+
+  xtag_skip_whitespace (parser);
+
+  value = xtag_slurp_quoted (parser);
+
+  if (value == NULL) {
+#ifdef XTAG_DEBUG
+    printf ("Got NULL quoted attribute value\n");
+#endif
+    goto err_free_name;
+  }
+
+  attr = malloc (sizeof (*attr));
+  attr->name = name;
+  attr->value = value;
+
+  return attr;
+
+ err_free_name:
+  free (name);
+
+  parser->valid = FALSE;
+
+  return NULL;
+}
+
+static XTag *
+xtag_parse_tag (XTagParser * parser)
+{
+  XTag * tag, * inner;
+  XAttribute * attr;
+  char * name;
+  char * pcdata;
+  char * s;
+
+  if (!parser->valid) return NULL;
+
+  if ((pcdata = xtag_slurp_to (parser, X_OPENTAG, X_NONE)) != NULL) {
+    tag = malloc (sizeof (*tag));
+    tag->name = NULL;
+    tag->pcdata = pcdata;
+    tag->parent = parser->current_tag;
+    tag->attributes = NULL;
+    tag->children = NULL;
+    tag->current_child = NULL;
+
+    return tag;
+  }
+
+  s = parser->start;
+
+  /* if this starts a close tag, return NULL and let the parent take it */
+  if (xtag_cin (s[0], X_OPENTAG) && xtag_cin (s[1], X_SLASH))
+    return NULL;
+
+  if (!xtag_assert_and_pass (parser, X_OPENTAG)) return NULL;
+
+  name = xtag_slurp_to (parser, X_WHITESPACE | X_SLASH | X_CLOSETAG, X_NONE);
+
+  if (name == NULL) return NULL;
+
+#ifdef XTAG_DEBUG
+  printf ("<%s ...\n", name);
+#endif
+
+  tag = malloc (sizeof (*tag));
+  tag->name = name;
+  tag->pcdata = NULL;
+  tag->parent = parser->current_tag;
+  tag->attributes = NULL;
+  tag->children = NULL;
+  tag->current_child = NULL;
+
+  s = parser->start;
+
+  if (xtag_cin (s[0], X_WHITESPACE)) {
+    while ((attr = xtag_parse_attribute (parser)) != NULL) {
+      tag->attributes = xlist_append (tag->attributes, attr);
+    }
+  }
+
+  xtag_skip_whitespace (parser);
+
+  s = parser->start;
+
+  if (xtag_cin (s[0], X_CLOSETAG)) {
+    parser->current_tag = tag;
+
+    xtag_assert_and_pass (parser, X_CLOSETAG);
+
+    while ((inner = xtag_parse_tag (parser)) != NULL) {
+      tag->children = xlist_append (tag->children, inner);
+    }
+
+    xtag_skip_whitespace (parser);
+
+    xtag_assert_and_pass (parser, X_OPENTAG);
+    xtag_assert_and_pass (parser, X_SLASH);
+    name = xtag_slurp_to (parser, X_WHITESPACE | X_CLOSETAG, X_NONE);
+    if (name) {
+      if (strcmp (name, tag->name)) {
+#ifdef XTAG_DEBUG
+        printf ("got %s expected %s\n", name, tag->name);
+#endif
+        parser->valid = FALSE;
+      }
+      free (name);
+    }
+
+    xtag_skip_whitespace (parser);
+    xtag_assert_and_pass (parser, X_CLOSETAG);
+
+  } else {
+    xtag_assert_and_pass (parser, X_SLASH);
+    xtag_assert_and_pass (parser, X_CLOSETAG);
+  }
+
+
+  return tag;
+}
+
+XTag *
+xtag_free (XTag * xtag)
+{
+  XList * l;
+  XAttribute * attr;
+  XTag * child;
+
+  if (xtag == NULL) return NULL;
+
+  if (xtag->name) free (xtag->name);
+  if (xtag->pcdata) free (xtag->pcdata);
+
+  for (l = xtag->attributes; l; l = l->next) {
+    if ((attr = (XAttribute *)l->data) != NULL) {
+      if (attr->name) free (attr->name);
+      if (attr->value) free (attr->value);
+      free (attr);
+    }
+  }
+  xlist_free (xtag->attributes);
+
+  for (l = xtag->children; l; l = l->next) {
+    child = (XTag *)l->data;
+    xtag_free (child);
+  }
+  xlist_free (xtag->children);
+
+  free (xtag);
+
+  return NULL;
+}
+
+XTag *
+xtag_new_parse (const char * s, int n)
+{
+  XTagParser parser;
+  XTag * tag, * ttag, * wrapper;
+
+  parser.valid = TRUE;
+  parser.current_tag = NULL;
+  parser.start = (char *)s;
+
+  if (n == -1)
+    parser.end = NULL;
+  else if (n == 0)
+    return NULL;
+  else
+    parser.end = (char *)&s[n];
+
+  tag = xtag_parse_tag (&parser);
+
+  if (!parser.valid) {
+    xtag_free (tag);
+    return NULL;
+  }
+
+  if ((ttag = xtag_parse_tag (&parser)) != NULL) {
+
+    if (!parser.valid) {
+      xtag_free (ttag);
+      return tag;
+    }
+
+    wrapper = malloc (sizeof (XTag));
+    wrapper->name = NULL;
+    wrapper->pcdata = NULL;
+    wrapper->parent = NULL;
+    wrapper->attributes = NULL;
+    wrapper->children = NULL;
+    wrapper->current_child = NULL;
+
+    wrapper->children = xlist_append (wrapper->children, tag);
+    wrapper->children = xlist_append (wrapper->children, ttag);
+
+    while ((ttag = xtag_parse_tag (&parser)) != NULL) {
+
+      if (!parser.valid) {
+        xtag_free (ttag);
+        return wrapper;
+      }
+
+      wrapper->children = xlist_append (wrapper->children, ttag);
+    }
+    return wrapper;
+  }
+
+  return tag;
+}
+
+char *
+xtag_get_name (XTag * xtag)
+{
+  return xtag ? xtag->name : NULL;
+}
+
+char *
+xtag_get_pcdata (XTag * xtag)
+{
+  XList * l;
+  XTag * child;
+
+  if (xtag == NULL) return NULL;
+
+  for (l = xtag->children; l; l = l->next) {
+    child = (XTag *)l->data;
+    if (child->pcdata != NULL) {
+      return child->pcdata;
+    }
+  }
+
+  return NULL;
+}
+
+char *
+xtag_get_attribute (XTag * xtag, char * attribute)
+{
+  XList * l;
+  XAttribute * attr;
+
+  if (xtag == NULL) return NULL;
+
+  for (l = xtag->attributes; l; l = l->next) {
+    if ((attr = (XAttribute *)l->data) != NULL) {
+      if (!strcmp (attr->name, attribute))
+        return attr->value;
+    }
+  }
+
+  return NULL;
+}
+
+XTag *
+xtag_first_child (XTag * xtag, char * name)
+{
+  XList * l;
+  XTag * child;
+
+  if (xtag == NULL) return NULL;
+
+  if ((l = xtag->children) == NULL) return NULL;
+
+  if (name == NULL) {
+    xtag->current_child = l;
+    return (XTag *)l->data;
+  }
+
+  for (; l; l = l->next) {
+    child = (XTag *)l->data;
+
+    if (!strcmp(child->name, name)) {
+      xtag->current_child = l;
+      return child;
+    }
+  }
+
+  xtag->current_child = NULL;
+
+  return NULL;
+}
+
+XTag *
+xtag_next_child (XTag * xtag, char * name)
+{
+  XList * l;
+  XTag * child;
+
+  if (xtag == NULL) return NULL;
+
+  if ((l = xtag->current_child) == NULL)
+    return xtag_first_child (xtag, name);
+
+  if ((l = l->next) == NULL)
+    return NULL;
+
+  if (name == NULL) {
+    xtag->current_child = l;
+    return (XTag *)l->data;
+  }
+
+  for (; l; l = l->next) {
+    child = (XTag *)l->data;
+
+    if (!strcmp(child->name, name)) {
+      xtag->current_child = l;
+      return child;
+    }
+  }
+
+  xtag->current_child = NULL;
+
+  return NULL;
+}
+
+/*
+ * This snprints function takes a variable list of char *, the last of
+ * which must be NULL, and prints each in turn to buf.
+ * Returns C99-style total length that would have been written, even if
+ * this is larger than n.
+ */
+static int
+xtag_snprints (char * buf, int n, ...)
+{
+  va_list ap;
+  char * s;
+  int len, to_copy, total = 0;
+
+  va_start (ap, n);
+  
+  for (s = va_arg (ap, char *); s; s = va_arg (ap, char *)) {
+    len = strlen (s);
+
+    if ((to_copy = MIN (n, len)) > 0) {
+      memcpy (buf, s, to_copy);
+      buf += to_copy;
+      n -= to_copy;
+    }
+
+    total += len;
+  }
+
+  va_end (ap);
+
+  return total;
+}
+
+int
+xtag_snprint (char * buf, int n, XTag * xtag)
+{
+  int nn, written = 0;
+  XList * l;
+  XAttribute * attr;
+  XTag * child;
+
+#define FORWARD(N) \
+  buf += MIN (n, N); \
+  n = MAX (n-N, 0);  \
+  written += N;
+
+  if (xtag == NULL) {
+    if (n > 0) buf[0] = '\0';
+    return 0;
+  }
+
+  if (xtag->pcdata) {
+    nn = xtag_snprints (buf, n, xtag->pcdata, NULL);
+    FORWARD(nn);
+
+    return written;
+  }
+
+  if (xtag->name) {
+    nn = xtag_snprints (buf, n, "<", xtag->name, NULL);
+    FORWARD(nn);
+
+    for (l = xtag->attributes; l; l = l->next) {
+      attr = (XAttribute *)l->data;
+      
+      nn = xtag_snprints (buf, n, " ", attr->name, "=\"", attr->value, "\"",
+                          NULL);
+      FORWARD(nn);
+    }
+    
+    if (xtag->children == NULL) {
+      nn = xtag_snprints (buf, n, "/>", NULL);
+      FORWARD(nn);
+
+      return written;
+    }
+    
+    nn = xtag_snprints (buf, n, ">", NULL);
+    FORWARD(nn);
+  }
+
+  for (l = xtag->children; l; l = l->next) {
+    child = (XTag *)l->data;
+
+    nn = xtag_snprint (buf, n, child);
+    FORWARD(nn);
+  }
+
+  if (xtag->name) {
+    nn = xtag_snprints (buf, n, "</", xtag->name, ">", NULL);
+    FORWARD(nn);
+  }
+
+  return written;
+}
+
diff --git a/modules/codec/cmml/xtag.h b/modules/codec/cmml/xtag.h
new file mode 100644 (file)
index 0000000..7ac6b71
--- /dev/null
@@ -0,0 +1,49 @@
+/*****************************************************************************
+ * xlist.h : a trivial parser for XML-like tags (header file)
+ *****************************************************************************
+ * Copyright (C) 2003-2004 Commonwealth Scientific and Industrial Research
+ *                         Organisation (CSIRO) Australia
+ * Copyright (C) 2000-2004 VideoLAN
+ *
+ * $Id$
+ *
+ * Authors: Conrad Parker <Conrad.Parker@csiro.au>
+ *          Andre Pang <Andre.Pang@csiro.au>
+ *
+ * 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., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
+ *****************************************************************************/
+
+#ifndef __XTAG_H__
+#define __XTAG_H__
+
+typedef void XTag;
+
+XTag * xtag_new_parse (const char * s, int n);
+
+char * xtag_get_name (XTag * xtag);
+
+char * xtag_get_pcdata (XTag * xtag);
+
+char * xtag_get_attribute (XTag * xtag, char * attribute);
+
+XTag * xtag_first_child (XTag * xtag, char * name);
+
+XTag * xtag_next_child (XTag * xtag, char * name);
+
+XTag * xtag_free (XTag * xtag);
+
+int xtag_snprint (char * buf, int n, XTag * xtag);
+
+#endif /* __XTAG_H__ */
diff --git a/modules/codec/cmml/xurl.c b/modules/codec/cmml/xurl.c
new file mode 100644 (file)
index 0000000..f43c99e
--- /dev/null
@@ -0,0 +1,490 @@
+/*****************************************************************************
+ * xurl.c: URL manipulation functions
+ *****************************************************************************
+ * Copyright (C) 2003-2004 Commonwealth Scientific and Industrial Research
+ *                         Organisation (CSIRO) Australia
+ * Copyright (C) 2004 VideoLAN
+ *
+ * $Id$
+ *
+ * Authors: Andre Pang <Andre.Pang@csiro.au>
+ *
+ * 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., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
+ *****************************************************************************/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "xurl.h"
+
+static char *streallocat( char *psz_string, char *psz_to_append );
+
+#ifndef HAVE_STRDUP
+static char *xurl_strdup( const char *psz_string );
+#else
+#define xurl_strdup strdup
+#endif
+
+static char *XURL_FindHostname          ( char *psz_url );
+static char *XURL_FindPath              ( char *psz_url );
+static char *XURL_FindFragment          ( char *psz_url );
+
+
+char *XURL_Join( char *psz_url1, char *psz_url2 )
+{
+    if( XURL_IsAbsolute( psz_url1 ) )
+        return XURL_Concat( psz_url1, psz_url2 );
+    else
+        return XURL_Concat( psz_url2, psz_url1 );
+
+    return NULL;
+}
+
+/* TODO: replace XURL_Concat's rel/absolute calculation with the one
+ * specified by RFC2396, and also test it on their test suite :) */
+
+
+char *XURL_Concat( char *psz_url, char *psz_append )
+{
+    char *psz_return_value = NULL;
+
+    if( XURL_IsAbsolute( psz_append ) == XURL_TRUE )
+        return strdup( psz_append );
+
+    if( XURL_IsAbsolute( psz_url ) )
+    {
+        if( XURL_HasAbsolutePath( psz_append ) )
+        {
+            char *psz_concat_url;
+
+            psz_concat_url = XURL_GetSchemeAndHostname( psz_url );
+
+            psz_concat_url = streallocat( psz_concat_url, psz_append );
+#ifdef XURL_DEBUG
+            fprintf( stderr, "XURL_Concat: concat is \"%s\"\n",
+                     psz_concat_url );
+#endif
+            psz_return_value = psz_concat_url;
+        }
+        else
+        {
+            /* psz_append is a relative URL */
+            char *psz_new_url;
+            
+            /* strip off last path component */
+            psz_new_url = XURL_GetHead( psz_url );
+            psz_new_url = streallocat( psz_new_url, psz_append );
+
+            psz_return_value = psz_new_url;
+        }
+    }
+    else
+    {
+        /* not an absolute URL */
+        if( XURL_HasAbsolutePath( psz_append ) == XURL_FALSE )
+        {
+            char *psz_new_url = XURL_GetHead( psz_url );
+
+            psz_new_url = streallocat( psz_new_url, psz_append );
+            psz_return_value = psz_new_url;
+        }
+        else
+        {
+            /* URL to append has an absolute path -- just use that instead */
+            psz_return_value = xurl_strdup( psz_append );
+        }
+    }
+
+    return psz_return_value;
+}
+
+
+XURL_Bool XURL_IsAbsolute( char *psz_url )
+{
+    if( XURL_FindHostname( psz_url ) == NULL )
+    {
+#ifdef XURL_DEBUG
+        fprintf( stderr, "XURL_IsAbsolute(%s) returning false\n", psz_url );
+#endif
+        return XURL_FALSE;
+    }
+    else
+    {
+#ifdef XURL_DEBUG
+        fprintf( stderr, "XURL_IsAbsolute(%s) returning true\n", psz_url );
+#endif
+        return XURL_TRUE;
+    }
+}
+
+
+XURL_Bool XURL_HasFragment( char *psz_url )
+{
+    if( XURL_FindFragment( psz_url ) == NULL )
+        return XURL_FALSE;
+    else
+        return XURL_TRUE;
+}
+
+
+char *XURL_FindHostname( char *psz_url )
+{
+    char *psz_return_value = NULL;
+
+    char *psz_scheme_separator = strstr( psz_url, "://" );
+    if( psz_scheme_separator != NULL)
+    {
+        char *psz_hostname = psz_scheme_separator + strlen( "://" );
+        if( *psz_hostname != '\0') psz_return_value = psz_hostname;
+
+#ifdef XURL_DEBUG
+        fprintf( stderr, "XURL_FindHostname(%s): returning \"%s\"\n",
+                 psz_url, psz_return_value );
+#endif
+    }
+
+    return psz_return_value;
+}
+
+
+XURL_Bool XURL_HasAbsolutePath( char *psz_url )
+{
+#ifdef XURL_WIN32_PATHING
+    if( psz_url[0] == '/' || psz_url[0] == '\\' )
+#else
+    if( psz_url[0] == '/' )
+#endif
+        return XURL_TRUE;
+    else
+        return XURL_FALSE;
+}
+
+
+char *XURL_GetHostname( char *psz_url )
+{
+    char *psz_return_value = NULL;
+    char *psz_hostname = XURL_FindHostname( psz_url );
+
+    if( psz_hostname != NULL )
+    {
+        char *psz_new_hostname;
+        size_t i_hostname_length;
+
+        char *psz_one_past_end_of_hostname = strchr( psz_hostname, '/' );
+        if( psz_one_past_end_of_hostname != NULL)
+        {
+            /* Found a '/' after the hostname, so copy characters between
+             * the hostname and the '/' to a new string */
+            i_hostname_length = psz_one_past_end_of_hostname -
+                psz_hostname;
+        }
+        else
+        {
+            /* Didn't find a '/', so copy from the start of the hostname
+             * until the end of the string */
+            i_hostname_length = strlen( psz_url ) - ( psz_hostname - psz_url );
+        }
+
+        /* Copy hostname to a new string */
+        psz_new_hostname = xurl_malloc( i_hostname_length );
+        if (psz_new_hostname == NULL) return NULL;
+        strncpy( psz_new_hostname, psz_hostname, i_hostname_length );
+
+#ifdef XURL_DEBUG
+        fprintf (stderr, "XURL_GetHostname: psz_new_hostname is \"%s\"\n",
+                 psz_new_hostname );
+#endif
+        psz_return_value = psz_new_hostname;
+    }
+    else
+    {
+        /* Didn't find a hostname */
+        return NULL;
+    }
+
+    return psz_return_value;
+}
+
+
+char *XURL_GetSchemeAndHostname( char *psz_url )
+{
+    char *psz_scheme, *psz_hostname, *psz_scheme_and_hostname;
+
+    psz_scheme = XURL_GetScheme( psz_url );
+    if( psz_scheme == NULL ) return NULL;
+
+    psz_hostname = XURL_GetHostname( psz_url );
+    if( psz_hostname == NULL ) return NULL;
+
+    /* malloc +1 for the terminating '\0' */
+    psz_scheme_and_hostname = xurl_malloc(
+            strlen( psz_scheme ) + strlen( "://" ) +
+            strlen( psz_hostname ) + 1);
+    if( psz_scheme_and_hostname == NULL ) return NULL;
+    (void) strcpy( psz_scheme_and_hostname, psz_scheme );
+    (void) strcat( psz_scheme_and_hostname, "://" );
+    (void) strcat( psz_scheme_and_hostname, psz_hostname );
+
+    if (psz_scheme_and_hostname == NULL ) return NULL;
+    return psz_scheme_and_hostname;
+}
+
+static 
+char *XURL_FindFragment( char *psz_url )
+{
+    char *pc_hash = NULL;
+    char *pc_return_value = NULL;
+    
+    pc_hash = strchr( psz_url, '#' );
+    if( pc_hash != NULL )
+    {
+        pc_return_value = pc_hash;
+    }
+
+    return pc_return_value;
+}
+
+
+char *XURL_FindQuery( char *psz_url )
+{
+    char *pc_question_mark = NULL;
+    char *pc_return_value = NULL;
+    
+    pc_question_mark = strchr( psz_url, '?' );
+    if( pc_question_mark != NULL )
+    {
+        pc_return_value = pc_question_mark;
+    }
+
+    return pc_return_value;
+}
+
+
+char *XURL_GetScheme( char *psz_url )
+{
+    char *psz_colon;
+    size_t i_scheme_length;
+    char *new_scheme;
+
+    if( XURL_IsAbsolute( psz_url ) == XURL_FALSE ) return strdup( "file" );
+
+    /* this strchr will always succeed since we have an absolute URL, and thus
+     * a scheme */
+    psz_colon = strchr( psz_url, ':' );
+
+    i_scheme_length = psz_colon - psz_url;
+
+    new_scheme = xurl_malloc( i_scheme_length );
+    if( new_scheme == NULL ) return NULL;
+
+    strncpy( new_scheme, psz_url, i_scheme_length );
+
+    return new_scheme;
+}
+
+
+XURL_Bool XURL_IsFileURL( char *psz_url )
+{
+    XURL_Bool b_return_value;
+    char *psz_scheme = XURL_GetScheme( psz_url );
+
+    if( strcasecmp( psz_scheme, "file" ) == 0 )
+        b_return_value = XURL_TRUE;
+    else
+        b_return_value = XURL_FALSE;
+
+    xurl_free( psz_scheme );
+
+    return b_return_value;
+}
+
+#ifndef HAVE_STRDUP
+static 
+char *xurl_strdup( const char *psz_string )
+{
+    size_t i_length;
+    char *psz_new_string;
+
+    if( !psz_string ) return NULL;
+    
+    i_length = strlen( psz_string ) + 1;
+    psz_new_string = (char *) xurl_malloc( i_length );
+    if( psz_new_string == NULL ) return NULL;
+
+    memcpy( psz_new_string, psz_string, i_length );
+
+    return psz_new_string;
+}
+#endif
+
+static 
+char *XURL_FindPath( char *psz_url )
+{
+    char *psz_return_value = NULL;
+
+    if( XURL_IsAbsolute( psz_url ) == XURL_TRUE )
+    {
+        char *psz_start_of_hostname = XURL_FindHostname( psz_url );
+        if( psz_start_of_hostname != NULL )
+        {
+            char *psz_start_of_path = strchr( psz_start_of_hostname, '/' );
+            psz_return_value = psz_start_of_path;
+        }
+    }
+    else
+    {
+        if( XURL_HasAbsolutePath( psz_url ) == XURL_TRUE )
+        {
+            psz_return_value = psz_url;
+        }
+        else
+        {
+            return xurl_strdup (".");
+        }
+    }
+
+    return psz_return_value;
+}
+
+
+char *XURL_GetPath( char *psz_url )
+{
+    char *psz_return_value = NULL;
+    char *psz_path = NULL;
+    char *pc_question_mark = NULL;
+    char *pc_fragment = NULL;
+
+    psz_path = xurl_strdup( XURL_FindPath( psz_url ) );
+#ifdef XURL_DEBUG
+    fprintf( stderr, "XURL_GetPath: XURL_FindPath returning \"%s\"\n",
+             psz_path );
+#endif
+    psz_return_value = psz_path;
+
+    pc_question_mark = XURL_FindQuery( psz_path );
+    if( pc_question_mark != NULL )
+    {
+        int i_path_length = pc_question_mark - psz_path;
+        *( psz_path + i_path_length ) = '\0';
+    }
+
+    pc_fragment = XURL_FindFragment( psz_path );
+    if( pc_fragment != NULL )
+    {
+#ifdef XURL_DEBUG
+        fprintf( stderr, "XURL_GetPath: XURL_FindFragment returned \"%s\"\n",
+                 pc_fragment );
+#endif
+        int i_path_length = pc_fragment - psz_path;
+        *( psz_path + i_path_length ) = '\0';
+    }
+
+#ifdef XURL_DEBUG
+    fprintf( stderr, "XURL_GetPath returning \"%s\"\n", psz_return_value );
+#endif
+
+    return psz_return_value;
+}
+
+
+char *XURL_GetHead( const char *psz_path )
+{
+    char *psz_path_head;
+    char *pc_last_slash;
+
+    /* kill everything up to the last / (including the /) */
+#ifdef XURL_WIN32_PATHING
+    /* Windows: Try looking for a \ first; if we don't find one, look for / */
+    pc_last_slash = strrchr( psz_path, '\\' );
+    if( pc_last_slash == NULL )
+        pc_last_slash = strrchr( psz_path, '/' );
+#else
+    pc_last_slash = strrchr( psz_path, '/' );
+#endif
+    if( pc_last_slash == NULL )
+    {
+        psz_path_head = xurl_strdup( psz_path );
+    }
+    else
+    {
+        size_t i_characters_until_last_slash;
+
+        i_characters_until_last_slash = pc_last_slash - psz_path;
+        psz_path_head = malloc(
+                ( i_characters_until_last_slash + 1 ) * sizeof(char) );
+        (void) strncpy( psz_path_head, psz_path,
+                i_characters_until_last_slash + 1 );
+
+        /* terminate the resulting string with '\0' */
+        *(psz_path_head +
+                i_characters_until_last_slash) = '\0';
+    }
+
+    /* append a trailing / */
+#ifdef XURL_WIN32_PATHING
+    streallocat( psz_path_head, "\\" );
+#else
+    streallocat( psz_path_head, "/" );
+#endif
+
+    return psz_path_head;
+}
+
+
+char *XURL_GetWithoutFragment( char *psz_url )
+{
+    char *psz_return_value = NULL;
+    char *psz_fragment;
+
+    psz_fragment = XURL_FindFragment( psz_url );
+    if( psz_fragment == NULL )
+    {
+        psz_return_value = xurl_strdup( psz_url );
+    }
+    else
+    {
+        size_t i_pre_fragment_length;
+        char *psz_without_fragment;
+
+        i_pre_fragment_length = psz_fragment - psz_url;
+
+        psz_without_fragment = xurl_malloc( i_pre_fragment_length + 1 );
+        if( psz_without_fragment == NULL )
+        {
+            psz_return_value = NULL;
+        }
+        else
+        {
+            memcpy( psz_without_fragment, psz_url, i_pre_fragment_length );
+            *( psz_without_fragment + i_pre_fragment_length ) = '\0';
+            psz_return_value = psz_without_fragment;
+        }
+    }
+    
+    return psz_return_value;
+}
+
+static 
+char *streallocat( char *psz_string, char *psz_to_append )
+{
+    size_t i_new_string_length = strlen( psz_string ) +
+        strlen( psz_to_append ) + 1;
+
+    psz_string = (char *) realloc( psz_string, i_new_string_length );
+    
+    return strcat( psz_string, psz_to_append );
+}
+
diff --git a/modules/codec/cmml/xurl.h b/modules/codec/cmml/xurl.h
new file mode 100644 (file)
index 0000000..da8fcd1
--- /dev/null
@@ -0,0 +1,68 @@
+/*****************************************************************************
+ * xurl.h: URL manipulation functions (header file)
+ *****************************************************************************
+ * Copyright (C) 2003-2004 Commonwealth Scientific and Industrial Research
+ *                         Organisation (CSIRO) Australia
+ * Copyright (C) 2004 VideoLAN
+ *
+ * $Id$
+ *
+ * Authors: Andre Pang <Andre.Pang@csiro.au>
+ *
+ * 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., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
+ *****************************************************************************/
+
+#ifndef __XURL_H__
+#define __XURL_H__
+
+#include <vlc/vlc.h>
+
+/* Specialise boolean definitions to VLC's boolean types */
+typedef vlc_bool_t XURL_Bool;
+#define XURL_FALSE VLC_FALSE
+#define XURL_TRUE VLC_TRUE
+
+/* Specialise general C functions to VLC's standards */
+#define xurl_malloc malloc
+#define xurl_free free
+
+/* Use DOS/Windows path separators? */
+#ifdef WIN32
+#  define XURL_WIN32_PATHING
+#else
+#  undef  XURL_WIN32_PATHING
+#endif
+
+/* Debugging */
+#undef XURL_DEBUG
+
+char *      XURL_Join                   ( char *psz_url1, char *psz_url2 );
+char *      XURL_Concat                 ( char *psz_url,  char *psz_append );
+
+XURL_Bool   XURL_IsAbsolute             ( char *psz_url );
+XURL_Bool   XURL_HasAbsolutePath        ( char *psz_url );
+XURL_Bool   XURL_IsFileURL              ( char *psz_url );
+XURL_Bool   XURL_HasFragment            ( char *psz_url );
+
+char *      XURL_GetHostname            ( char *psz_url );
+char *      XURL_GetSchemeAndHostname   ( char *psz_url );
+char *      XURL_GetScheme              ( char *psz_url );
+char *      XURL_GetPath                ( char *psz_url );
+char *      XURL_GetWithoutFragment     ( char *psz_url );
+
+char *      XURL_GetHead                ( const char *psz_path );
+
+#endif /* __XURL_H__ */
+