]> git.sesse.net Git - vlc/blobdiff - src/misc/unicode.c
Use //translit when converting to an non-Unicode charset
[vlc] / src / misc / unicode.c
index 09daff1618d6c9ce66ba4298130613e93bc83907..081187b5141af085bb81292513c99a9fd1c43a53 100644 (file)
@@ -1,7 +1,7 @@
 /*****************************************************************************
  * unicode.c: UTF8 <-> locale functions
  *****************************************************************************
- * Copyright (C) 2005 the VideoLAN team
+ * Copyright (C) 2005-2006 the VideoLAN team
  * $Id$
  *
  * Authors: RĂ©mi Denis-Courmont <rem # videolan.org>
  *****************************************************************************/
 #include <vlc/vlc.h>
 #include "charset.h"
+
+#include <assert.h>
+
 #include <stdio.h>
+#include <stdarg.h>
+#include <errno.h>
+#include <sys/types.h>
+#ifdef HAVE_DIRENT_H
+#  include <dirent.h>
+#endif
+#ifdef UNDER_CE
+#  include <tchar.h>
+#endif
+#ifdef HAVE_SYS_STAT_H
+# include <sys/stat.h>
+#endif
+#ifndef HAVE_LSTAT
+# define lstat( a, b ) stat(a, b)
+#endif
+
+#ifdef __APPLE__
+/* Define this if the OS always use UTF-8 internally */
+# define ASSUME_UTF8 1
+#endif
+
+#if !(defined (WIN32) || defined (UNDER_CE) || defined (ASSUME_UTF8))
+# define USE_ICONV 1
+#endif
+
+#if defined (USE_ICONV) && !defined (HAVE_ICONV)
+# error No UTF8 charset conversion implemented on this platform!
+#endif
+
+
+
+#ifdef USE_ICONV
+static struct {
+    vlc_iconv_t hd;
+    vlc_mutex_t lock;
+} from_locale, to_locale;
+#endif
+
+void LocaleInit( vlc_object_t *p_this )
+{
+#ifdef USE_ICONV
+    char *psz_charset;
+
+    if( vlc_current_charset( &psz_charset ) )
+        /* UTF-8 */
+        from_locale.hd = to_locale.hd = (vlc_iconv_t)(-1);
+    else
+    {
+        /* not UTF-8 */
+        char psz_buf[strlen( psz_charset ) + sizeof( "//translist" )];
+        const char *psz_conv;
+
+        /*
+         * Still allow non-ASCII characters when the locale is not set.
+         * Western Europeans are being favored for historical reasons.
+         */
+        if( strcmp( psz_charset, "ASCII" ) )
+        {
+            sprintf( psz_buf, "%s//translit", psz_charset );
+            psz_conv = psz_buf;
+        }
+        else
+            psz_conv = "ISO-8859-1//translit";
+
+        vlc_mutex_init( p_this, &from_locale.lock );
+        vlc_mutex_init( p_this, &to_locale.lock );
+        from_locale.hd = vlc_iconv_open( "UTF-8", psz_conv );
+        to_locale.hd = vlc_iconv_open( psz_conv, "UTF-8" );
+    }
+
+    free( psz_charset );
+
+    assert( (from_locale.hd == (vlc_iconv_t)(-1))
+            == (to_locale.hd == (vlc_iconv_t)(-1)) );
+#else
+    (void)p_this;
+#endif
+}
+
+void LocaleDeinit( void )
+{
+#ifdef USE_ICONV
+    if( to_locale.hd != (vlc_iconv_t)(-1) )
+    {
+        vlc_iconv_close( to_locale.hd );
+        vlc_mutex_destroy( &to_locale.lock );
+    }
+
+    if( from_locale.hd != (vlc_iconv_t)(-1) )
+    {
+        vlc_iconv_close( from_locale.hd );
+        vlc_mutex_destroy( &from_locale.lock );
+    }
+#endif
+}
+
+#if defined (WIN32) || defined (UNDER_CE)
+static char *MB2MB( const char *string, UINT fromCP, UINT toCP )
+{
+    char *out;
+    wchar_t *wide;
+    int len;
+
+    len = MultiByteToWideChar( fromCP, 0, string, -1, NULL, 0 );
+    assert( len > 0 );
+    wide = (wchar_t *)malloc (len * sizeof (wchar_t));
+    if( wide == NULL )
+        return NULL;
+
+    MultiByteToWideChar( fromCP, 0, string, -1, wide, len );
+    len = WideCharToMultiByte( toCP, 0, wide, -1, NULL, 0, NULL, NULL );
+    assert( len > 0 );
+    out = malloc( len );
+
+    WideCharToMultiByte( toCP, 0, wide, -1, out, len, NULL, NULL );
+    free( wide );
+    return out;
+}
+#endif
+
+/*****************************************************************************
+ * FromLocale: converts a locale string to UTF-8
+ *****************************************************************************/
+char *FromLocale( const char *locale )
+{
+    if( locale == NULL )
+        return NULL;
+
+#if !(defined WIN32 || defined (UNDER_CE))
+# ifdef USE_ICONV
+    if( from_locale.hd != (vlc_iconv_t)(-1) )
+    {
+        char *iptr = (char *)locale, *output, *optr;
+        size_t inb, outb;
+
+        /*
+         * We are not allowed to modify the locale pointer, even if we cast it
+         * to non-const.
+         */
+        inb = strlen( locale );
+        /* FIXME: I'm not sure about the value for the multiplication
+         * (for western people, multiplication by 3 (Latin9) is needed).
+         * While UTF-8 could reach 6 bytes, no existing code point exceeds
+         * 4 bytes. */
+        outb = inb * 4 + 1;
+
+        optr = output = malloc( outb );
+
+        vlc_mutex_lock( &from_locale.lock );
+        vlc_iconv( from_locale.hd, NULL, NULL, NULL, NULL );
+
+        while( vlc_iconv( from_locale.hd, &iptr, &inb, &optr, &outb )
+               == (size_t)-1 )
+        {
+            *optr++ = '?';
+            outb--;
+            iptr++;
+            inb--;
+            vlc_iconv( from_locale.hd, NULL, NULL, NULL, NULL );
+        }
+        vlc_mutex_unlock( &from_locale.lock );
+        *optr = '\0';
+
+        assert (inb == 0);
+        assert (*iptr == '\0');
+        assert (*optr == '\0');
+        assert (strlen( output ) == (size_t)(optr - output));
+        return realloc( output, optr - output + 1 );
+    }
+# endif /* USE_ICONV */
+    return (char *)locale;
+#else /* WIN32 */
+    return MB2MB( locale, CP_ACP, CP_UTF8 );
+#endif
+}
+
+char *FromLocaleDup( const char *locale )
+{
+#if defined (ASSUME_UTF8)
+    return strdup( locale );
+#else
+# ifdef USE_ICONV
+    if (from_locale.hd == (vlc_iconv_t)(-1))
+        return strdup( locale );
+# endif
+    return FromLocale( locale );
+#endif
+}
+
+
+/*****************************************************************************
+ * ToLocale: converts an UTF-8 string to locale
+ *****************************************************************************/
+char *ToLocale( const char *utf8 )
+{
+    if( utf8 == NULL )
+        return NULL;
+
+#if !(defined (WIN32) || defined (UNDER_CE))
+# ifdef USE_ICONV
+    if( to_locale.hd != (vlc_iconv_t)(-1) )
+    {
+        char *iptr = (char *)utf8, *output, *optr;
+        size_t inb, outb;
+
+        /*
+        * We are not allowed to modify the locale pointer, even if we cast it
+        * to non-const.
+        */
+        inb = strlen( utf8 );
+        /* FIXME: I'm not sure about the value for the multiplication
+        * (for western people, multiplication is not needed) */
+        outb = inb * 2 + 1;
+
+        optr = output = malloc( outb );
+        vlc_mutex_lock( &to_locale.lock );
+        vlc_iconv( to_locale.hd, NULL, NULL, NULL, NULL );
+
+        while( vlc_iconv( to_locale.hd, &iptr, &inb, &optr, &outb )
+               == (size_t)-1 )
+        {
+            *optr++ = '?'; /* should not happen, and yes, it sucks */
+            outb--;
+            iptr++;
+            inb--;
+            vlc_iconv( to_locale.hd, NULL, NULL, NULL, NULL );
+        }
+        vlc_mutex_unlock( &to_locale.lock );
+        *optr = '\0';
+
+        assert (inb == 0);
+        assert (*iptr == '\0');
+        assert (*optr == '\0');
+        assert (strlen( output ) == (size_t)(optr - output));
+        return realloc( output, optr - output + 1 );
+    }
+# endif /* USE_ICONV */
+    return (char *)utf8;
+#else /* WIN32 */
+    return MB2MB( utf8, CP_UTF8, CP_ACP );
+#endif
+}
+
+char *ToLocaleDup( const char *utf8 )
+{
+#if defined (ASSUME_UTF8)
+    return strdup( utf8 );
+#else
+# ifdef USE_ICONV
+    if (to_locale.hd == (vlc_iconv_t)(-1))
+        return strdup( utf8 );
+# endif
+    return ToLocale( utf8 );
+#endif
+}
+
+void LocaleFree( const char *str )
+{
+#ifdef USE_ICONV
+    if( to_locale.hd == (vlc_iconv_t)(-1) )
+        return;
+#endif
+
+#ifndef ASSUME_UTF8
+    if( str != NULL )
+        free( (char *)str );
+#endif
+}
 
 /*****************************************************************************
- * vlc_fopen: Calls fopen() after conversion of file name to OS locale
+ * utf8_fopen: Calls fopen() after conversion of file name to OS locale
  *****************************************************************************/
-FILE *vlc_fopen( const char *filename, const char *mode )
+FILE *utf8_fopen( const char *filename, const char *mode )
 {
+#if !(defined (WIN32) || defined (UNDER_CE))
     const char *local_name = ToLocale( filename );
 
     if( local_name != NULL )
@@ -41,14 +313,197 @@ FILE *vlc_fopen( const char *filename, const char *mode )
         LocaleFree( local_name );
         return stream;
     }
+    else
+        errno = ENOENT;
+    return NULL;
+#else
+    wchar_t wpath[MAX_PATH + 1];
+    size_t len = strlen( mode ) + 1;
+    wchar_t wmode[len];
+
+    if( !MultiByteToWideChar( CP_UTF8, 0, filename, -1, wpath, MAX_PATH )
+     || !MultiByteToWideChar( CP_ACP, 0, mode, len, wmode, len ) )
+    {
+        errno = ENOENT;
+        return NULL;
+    }
+    wpath[MAX_PATH] = L'\0';
+
+    return _wfopen( wpath, wmode );
+#endif
+}
+
+/*****************************************************************************
+ * utf8_mkdir: Calls mkdir() after conversion of file name to OS locale
+ *****************************************************************************/
+int utf8_mkdir( const char *dirname )
+{
+#if defined (UNDER_CE) || defined (WIN32)
+    wchar_t wname[MAX_PATH + 1];
+    char mod[MAX_PATH + 1];
+    int i;
+
+    /* Convert '/' into '\' */
+    for( i = 0; *dirname; i++ )
+    {
+        if( i == MAX_PATH )
+            return -1; /* overflow */
+
+        if( *dirname == '/' )
+            mod[i] = '\\';
+        else
+            mod[i] = *dirname;
+        dirname++;
+
+    }
+    mod[i] = 0;
+
+    if( MultiByteToWideChar( CP_UTF8, 0, mod, -1, wname, MAX_PATH ) == 0 )
+    {
+        errno = ENOENT;
+        return -1;
+    }
+    wname[MAX_PATH] = L'\0';
+
+    if( CreateDirectoryW( wname, NULL ) == 0 )
+    {
+        if( GetLastError( ) == ERROR_ALREADY_EXISTS )
+            errno = EEXIST;
+        errno = ENOENT;
+        return -1;
+    }
+    return 0;
+#else
+    char *locname = ToLocale( dirname );
+    int res;
+
+    if( locname == NULL )
+    {
+        errno = ENOENT;
+        return -1;
+    }
+    res = mkdir( locname, 0755 );
+
+    LocaleFree( locname );
+    return res;
+#endif
+}
+
+
+void *utf8_opendir( const char *dirname )
+{
+    const char *local_name = ToLocale( dirname );
+
+    if( local_name != NULL )
+    {
+        DIR *dir = opendir( local_name );
+        LocaleFree( local_name );
+        return dir;
+    }
+    else
+        errno = ENOENT;
     return NULL;
 }
 
+const char *utf8_readdir( void *dir )
+{
+    struct dirent *ent;
+
+    ent = readdir( (DIR *)dir );
+    if( ent == NULL )
+        return NULL;
+
+    return FromLocale( ent->d_name );
+}
+
+
+static int utf8_statEx( const char *filename, void *buf,
+                        vlc_bool_t deref )
+{
+#if !(defined (WIN32) || defined (UNDER_CE))
+# ifdef HAVE_SYS_STAT_H
+    const char *local_name = ToLocale( filename );
+
+    if( local_name != NULL )
+    {
+        int res = deref ? stat( local_name, (struct stat *)buf )
+                       : lstat( local_name, (struct stat *)buf );
+        LocaleFree( local_name );
+        return res;
+    }
+    errno = ENOENT;
+# endif
+    return -1;
+#else
+    wchar_t wpath[MAX_PATH + 1];
+
+    if( !MultiByteToWideChar( CP_UTF8, 0, filename, -1, wpath, MAX_PATH ) )
+    {
+        errno = ENOENT;
+        return -1;
+    }
+    wpath[MAX_PATH] = L'\0';
+
+    /* struct _stat is just a silly Microsoft alias for struct stat */
+    return _wstat( wpath, (struct _stat *)buf );
+#endif
+}
+
+
+int utf8_stat( const char *filename, void *buf)
+{
+    return utf8_statEx( filename, buf, VLC_TRUE );
+}
+
+int utf8_lstat( const char *filename, void *buf)
+{
+    return utf8_statEx( filename, buf, VLC_FALSE );
+}
+
+/*****************************************************************************
+ * utf8_*printf: *printf with conversion from UTF-8 to local encoding
+ *****************************************************************************/
+static int utf8_vasprintf( char **str, const char *fmt, va_list ap )
+{
+       char *utf8;
+       int res = vasprintf( &utf8, fmt, ap );
+       if( res == -1 )
+               return -1;
+
+       *str = ToLocaleDup( utf8 );
+       free( utf8 );
+       return res;
+}
+
+static int utf8_vfprintf( FILE *stream, const char *fmt, va_list ap )
+{
+       char *str;
+       int res = utf8_vasprintf( &str, fmt, ap );
+       if( res == -1 )
+               return -1;
+
+       fputs( str, stream );
+       free( str );
+       return res;
+}
+
+int utf8_fprintf( FILE *stream, const char *fmt, ... )
+{
+       va_list ap;
+       int res;
+
+       va_start( ap, fmt );
+       res = utf8_vfprintf( stream, fmt, ap );
+       va_end( ap );
+       return res;
+}
+
 /*****************************************************************************
  * EnsureUTF8: replaces invalid/overlong UTF-8 sequences with question marks
  *****************************************************************************
  * Not Todo : convert Latin1 to UTF-8 on the flu
  * It is not possible given UTF-8 needs more space
+ * Returns str if it was valid UTF-8, NULL if not.
  *****************************************************************************/
 #define isutf8cont( c ) (((c) >= 0x80) && ((c) <= 0xBF)) 
 char *EnsureUTF8( char *str )
@@ -72,7 +527,10 @@ char *EnsureUTF8( char *str )
             if( isutf8cont( c ) )
                 ptr += 2; /* OK */
             else
+            {
                 *ptr++ = '?'; /* invalid */
+                str = NULL;
+            }
         }
         else
         /* 3 bytes */
@@ -85,10 +543,16 @@ char *EnsureUTF8( char *str )
                 if( isutf8cont( c ) )
                     ptr += 3; /* OK */
                 else
+                {
                     *ptr++ = '?';
+                    str = NULL;
+                }
             }
             else
+            {
                 *ptr++ = '?';
+                str = NULL;
+            }
         }
         else
         if( ( ( c >= 0xE1 ) && ( c <= 0xEC ) ) || ( c == 0xEC )
@@ -101,10 +565,16 @@ char *EnsureUTF8( char *str )
                 if( isutf8cont( c ) )
                     ptr += 3; /* OK */
                 else
+                {
                     *ptr++ = '?';
+                    str = NULL;
+                }
             }
             else
+            {
                 *ptr++ = '?';
+                str = NULL;
+            }
         }
         else
         if( c == 0xED )
@@ -116,10 +586,16 @@ char *EnsureUTF8( char *str )
                 if( isutf8cont( c ) )
                     ptr += 3; /* OK */
                 else
+                {
                     *ptr++ = '?';
+                    str = NULL;
+                }
             }
             else
+            {
                 *ptr++ = '?';
+                str = NULL;
+            }
         }
         else
         /* 4 bytes */
@@ -135,13 +611,22 @@ char *EnsureUTF8( char *str )
                     if( isutf8cont( c ) )
                         ptr += 4; /* OK */
                     else
+                    {
                         *ptr++ = '?';
+                        str = NULL;
+                    }
                 }
                 else
+                {
                     *ptr++ = '?';
+                    str = NULL;
+                }
             }
             else
+            {
                 *ptr++ = '?';
+                str = NULL;
+            }
         }
         else
         if( ( c >= 0xF1 ) && ( c <= 0xF3 ) )
@@ -156,13 +641,22 @@ char *EnsureUTF8( char *str )
                     if( isutf8cont( c ) )
                         ptr += 4; /* OK */
                     else
+                    {
                         *ptr++ = '?';
+                        str = NULL;
+                    }
                 }
                 else
+                {
                     *ptr++ = '?';
+                    str = NULL;
+                }
             }
             else
+            {
                 *ptr++ = '?';
+                str = NULL;
+            }
         }
         else
         if( c == 0xF4 )
@@ -177,16 +671,28 @@ char *EnsureUTF8( char *str )
                     if( isutf8cont( c ) )
                         ptr += 4; /* OK */
                     else
+                    {
                         *ptr++ = '?';
+                        str = NULL;
+                    }
                 }
                 else
+                {
                     *ptr++ = '?';
+                    str = NULL;
+                }
             }
             else
+            {
                 *ptr++ = '?';
+                str = NULL;
+            }
         }
         else
+        {
             *ptr++ = '?';
+            str = NULL;
+        }
     }
 
     return str;