root/ext/intl/locale/locale_methods.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. findOffset
  2. getPreferredTag
  3. getStrrtokenPos
  4. getSingletonPos
  5. PHP_NAMED_FUNCTION
  6. PHP_NAMED_FUNCTION
  7. get_icu_value_internal
  8. get_icu_value_src_php
  9. PHP_FUNCTION
  10. PHP_FUNCTION
  11. PHP_FUNCTION
  12. get_icu_disp_value_src_php
  13. PHP_FUNCTION
  14. PHP_FUNCTION
  15. PHP_FUNCTION
  16. PHP_FUNCTION
  17. PHP_FUNCTION
  18. PHP_FUNCTION
  19. PHP_FUNCTION
  20. append_key_value
  21. add_prefix
  22. append_multiple_key_values
  23. handleAppendResult
  24. PHP_FUNCTION
  25. get_private_subtags
  26. add_array_entry
  27. PHP_FUNCTION
  28. PHP_FUNCTION
  29. strToMatch
  30. PHP_FUNCTION
  31. array_cleanup
  32. lookup_loc_range
  33. PHP_FUNCTION
  34. PHP_FUNCTION

/*
   +----------------------------------------------------------------------+
   | PHP Version 5                                                        |
   +----------------------------------------------------------------------+
   | This source file is subject to version 3.01 of the PHP license,      |
   | that is bundled with this package in the file LICENSE, and is        |
   | available through the world-wide-web at the following url:           |
   | http://www.php.net/license/3_01.txt                                  |
   | If you did not receive a copy of the PHP license and are unable to   |
   | obtain it through the world-wide-web, please send a note to          |
   | license@php.net so we can mail you a copy immediately.               |
   +----------------------------------------------------------------------+
   | Authors: Kirti Velankar <kirtig@yahoo-inc.com>                       |
   +----------------------------------------------------------------------+
*/

/* $Id$ */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <unicode/ustring.h>
#include <unicode/udata.h>
#include <unicode/putil.h>
#include <unicode/ures.h>

#include "php_intl.h"
#include "locale.h"
#include "locale_class.h"
#include "locale_methods.h"
#include "intl_convert.h"
#include "intl_data.h"

#include <zend_API.h>
#include <zend.h>
#include <php.h>
#include "main/php_ini.h"
#include "ext/standard/php_smart_str.h"

ZEND_EXTERN_MODULE_GLOBALS( intl )

/* Sizes required for the strings "variant15" , "extlang11", "private12" etc. */
#define SEPARATOR "_"
#define SEPARATOR1 "-"
#define DELIMITER "-_"
#define EXTLANG_PREFIX "a"
#define PRIVATE_PREFIX "x"
#define DISP_NAME "name"

#define MAX_NO_VARIANT  15
#define MAX_NO_EXTLANG  3
#define MAX_NO_PRIVATE  15
#define MAX_NO_LOOKUP_LANG_TAG  100

#define LOC_NOT_FOUND 1

/* Sizes required for the strings "variant15" , "extlang3", "private12" etc. */
#define VARIANT_KEYNAME_LEN  11
#define EXTLANG_KEYNAME_LEN  10
#define PRIVATE_KEYNAME_LEN  11

/* Based on IANA registry at the time of writing this code
*
*/
static const char * const LOC_GRANDFATHERED[] = {
        "art-lojban",           "i-klingon",            "i-lux",                        "i-navajo",             "no-bok",               "no-nyn",
        "cel-gaulish",          "en-GB-oed",            "i-ami",                
        "i-bnn",                "i-default",            "i-enochian",   
        "i-mingo",              "i-pwn",                "i-tao", 
        "i-tay",                "i-tsu",                "sgn-BE-fr",
        "sgn-BE-nl",            "sgn-CH-de",            "zh-cmn",
        "zh-cmn-Hans",          "zh-cmn-Hant",          "zh-gan" ,
        "zh-guoyu",             "zh-hakka",             "zh-min",
        "zh-min-nan",           "zh-wuu",               "zh-xiang",     
        "zh-yue",               NULL
};

/* Based on IANA registry at the time of writing this code
*  This array lists the preferred values for the grandfathered tags if applicable
*  This is in sync with the array LOC_GRANDFATHERED      
*  e.g. the offsets of the grandfathered tags match the offset of the preferred  value
*/
static const int                LOC_PREFERRED_GRANDFATHERED_LEN = 6;
static const char * const       LOC_PREFERRED_GRANDFATHERED[]  = {
        "jbo",                  "tlh",                  "lb",
        "nv",                   "nb",                   "nn",                   
        NULL
};

/*returns TRUE if a is an ID separator FALSE otherwise*/
#define isIDSeparator(a) (a == '_' || a == '-')
#define isKeywordSeparator(a) (a == '@' )
#define isEndOfTag(a) (a == '\0' )

#define isPrefixLetter(a) ((a=='x')||(a=='X')||(a=='i')||(a=='I'))

/*returns TRUE if one of the special prefixes is here (s=string)
  'x-' or 'i-' */
#define isIDPrefix(s) (isPrefixLetter(s[0])&&isIDSeparator(s[1]))
#define isKeywordPrefix(s) ( isKeywordSeparator(s[0]) )

/* Dot terminates it because of POSIX form  where dot precedes the codepage
 * except for variant */
#define isTerminator(a)  ((a==0)||(a=='.')||(a=='@'))

/* {{{ return the offset of 'key' in the array 'list'.
 * returns -1 if not present */
static int16_t findOffset(const char* const* list, const char* key)
{
        const char* const* anchor = list;
        while (*list != NULL) {
                if (strcmp(key, *list) == 0) {
                        return (int16_t)(list - anchor);
                }
                list++;
        }

        return -1;

}
/*}}}*/

static char* getPreferredTag(const char* gf_tag)
{ 
        char* result = NULL;
        int grOffset = 0;

        grOffset = findOffset( LOC_GRANDFATHERED ,gf_tag);
        if(grOffset < 0) {
                return NULL;
        }
        if( grOffset < LOC_PREFERRED_GRANDFATHERED_LEN ){
                /* return preferred tag */
                result = estrdup( LOC_PREFERRED_GRANDFATHERED[grOffset] );
        } else {
                /* Return correct grandfathered language tag */
                result = estrdup( LOC_GRANDFATHERED[grOffset] );
        }
        return result;
}

/* {{{
* returns the position of next token for lookup 
* or -1 if no token
* strtokr equivalent search for token in reverse direction 
*/
static int getStrrtokenPos(char* str, int savedPos)
{
        int result =-1;
        int i;
        
        for(i=savedPos-1; i>=0; i--) {
                if(isIDSeparator(*(str+i)) ){
                        /* delimiter found; check for singleton */
                        if(i>=2 && isIDSeparator(*(str+i-2)) ){
                                /* a singleton; so send the position of token before the singleton */
                                result = i-2;
                        } else {
                                result = i;
                        }
                        break;
                }
        }
        if(result < 1){
                /* Just in case inavlid locale e.g. '-x-xyz' or '-sl_Latn' */
                result =-1;
        }
        return result;
}
/* }}} */

/* {{{
* returns the position of a singleton if present 
* returns -1 if no singleton
* strtok equivalent search for singleton
*/
static int getSingletonPos(const char* str)
{
        int result =-1;
        int i=0;
        int len = 0;
        
        if( str && ((len=strlen(str))>0) ){
                for( i=0; i<len ; i++){
                        if( isIDSeparator(*(str+i)) ){
                                if( i==1){
                                        /* string is of the form x-avy or a-prv1 */
                                        result =0;
                                        break;
                                } else {
                                        /* delimiter found; check for singleton */
                                        if( isIDSeparator(*(str+i+2)) ){
                                                /* a singleton; so send the position of separator before singleton */
                                                result = i+1;
                                                break;
                                        }
                                }
                        }
                }/* end of for */
                
        }
        return result;
}
/* }}} */

/* {{{ proto static string Locale::getDefault(  )
   Get default locale */
/* }}} */
/* {{{ proto static string locale_get_default( )
   Get default locale */
PHP_NAMED_FUNCTION(zif_locale_get_default)
{
        RETURN_STRING( intl_locale_get_default( TSRMLS_C ), TRUE );
}

/* }}} */

/* {{{ proto static string Locale::setDefault( string $locale )
   Set default locale */
/* }}} */
/* {{{ proto static string locale_set_default( string $locale )
   Set default locale */
PHP_NAMED_FUNCTION(zif_locale_set_default)
{
        char* locale_name = NULL;
        int   len=0;    

        if(zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC,  "s",
                &locale_name ,&len ) == FAILURE)
        {
                intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR,
                                "locale_set_default: unable to parse input params", 0 TSRMLS_CC );

                RETURN_FALSE;
        }

        if(len == 0) {
                locale_name =  (char *)uloc_getDefault() ;
                len = strlen(locale_name);
        }

        zend_alter_ini_entry(LOCALE_INI_NAME, sizeof(LOCALE_INI_NAME), locale_name, len, PHP_INI_USER, PHP_INI_STAGE_RUNTIME);  

        RETURN_TRUE;
}
/* }}} */

/* {{{
* Gets the value from ICU 
* common code shared by get_primary_language,get_script or get_region or get_variant
* result = 0 if error, 1 if successful , -1 if no value
*/
static char* get_icu_value_internal( const char* loc_name , char* tag_name, int* result , int fromParseLocale)
{
        char*           tag_value       = NULL;
        int32_t         tag_value_len   = 512;

        int             singletonPos    = 0;
        char*           mod_loc_name    = NULL;
        int             grOffset        = 0;

        int32_t         buflen          = 512;
        UErrorCode      status          = U_ZERO_ERROR;


        if( strcmp(tag_name, LOC_CANONICALIZE_TAG) != 0 ){
                /* Handle  grandfathered languages */
                grOffset =  findOffset( LOC_GRANDFATHERED , loc_name );
                if( grOffset >= 0 ){
                        if( strcmp(tag_name , LOC_LANG_TAG)==0 ){
                                return estrdup(loc_name);
                        } else {
                                /* Since Grandfathered , no value , do nothing , retutn NULL */
                                return NULL;
                        }
                }

        if( fromParseLocale==1 ){
                /* Handle singletons */
                if( strcmp(tag_name , LOC_LANG_TAG)==0 ){
                        if( strlen(loc_name)>1 && (isIDPrefix(loc_name) == 1) ){
                                return estrdup(loc_name);
                        }
                }

                singletonPos = getSingletonPos( loc_name );     
                if( singletonPos == 0){
                        /* singleton at start of script, region , variant etc.
                         * or invalid singleton at start of language */
                        return NULL;
                } else if( singletonPos > 0 ){
                        /* singleton at some position except at start
                         * strip off the singleton and rest of the loc_name */
                        mod_loc_name = estrndup ( loc_name , singletonPos-1);
                }
        } /* end of if fromParse */

        } /* end of if != LOC_CANONICAL_TAG */

        if( mod_loc_name == NULL){
                mod_loc_name = estrdup(loc_name );      
        }

        /* Proceed to ICU */
    do{
                tag_value = erealloc( tag_value , buflen  );
                tag_value_len = buflen;

                if( strcmp(tag_name , LOC_SCRIPT_TAG)==0 ){
                        buflen = uloc_getScript ( mod_loc_name ,tag_value , tag_value_len , &status);
                }
                if( strcmp(tag_name , LOC_LANG_TAG )==0 ){
                        buflen = uloc_getLanguage ( mod_loc_name ,tag_value , tag_value_len , &status);
                }
                if( strcmp(tag_name , LOC_REGION_TAG)==0 ){
                        buflen = uloc_getCountry ( mod_loc_name ,tag_value , tag_value_len , &status);
                }
                if( strcmp(tag_name , LOC_VARIANT_TAG)==0 ){
                        buflen = uloc_getVariant ( mod_loc_name ,tag_value , tag_value_len , &status);
                }
                if( strcmp(tag_name , LOC_CANONICALIZE_TAG)==0 ){
                        buflen = uloc_canonicalize ( mod_loc_name ,tag_value , tag_value_len , &status);
                }

                if( U_FAILURE( status ) ) {
                        if( status == U_BUFFER_OVERFLOW_ERROR ) {
                                status = U_ZERO_ERROR;
                                continue;
                        }

                        /* Error in retriving data */
                        *result = 0;
                        if( tag_value ){
                                efree( tag_value );
                        }
                        if( mod_loc_name ){
                                efree( mod_loc_name);
                        }
                        return NULL;
                }
        } while( buflen > tag_value_len );

        if(  buflen ==0 ){
                /* No value found */
                *result = -1;
                if( tag_value ){
                        efree( tag_value );
                }
                if( mod_loc_name ){
                        efree( mod_loc_name);
                }
                return NULL;
        } else {
                *result = 1;
        }

        if( mod_loc_name ){
                efree( mod_loc_name);
        }
        return tag_value;
}
/* }}} */

/* {{{
* Gets the value from ICU , called when PHP userspace function is called
* common code shared by get_primary_language,get_script or get_region or get_variant
*/
static void get_icu_value_src_php( char* tag_name, INTERNAL_FUNCTION_PARAMETERS) 
{

        const char* loc_name            = NULL;
        int         loc_name_len        = 0;

        char*       tag_value           = NULL;
        char*       empty_result        = "";

        int         result              = 0;
        char*       msg                 = NULL;

        UErrorCode  status              = U_ZERO_ERROR;

        intl_error_reset( NULL TSRMLS_CC );

        if(zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "s",
        &loc_name ,&loc_name_len ) == FAILURE) {
                spprintf(&msg , 0, "locale_get_%s : unable to parse input params", tag_name );
                intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR,  msg , 1 TSRMLS_CC );
                efree(msg);

                RETURN_FALSE;
    }

        if(loc_name_len == 0) {
                loc_name = intl_locale_get_default(TSRMLS_C);
        }

        /* Call ICU get */
        tag_value = get_icu_value_internal( loc_name , tag_name , &result ,0);

        /* No value found */
        if( result == -1 ) {
                if( tag_value){
                        efree( tag_value);
                }
                RETURN_STRING( empty_result , TRUE);
        }

        /* value found */
        if( tag_value){
                RETURN_STRING( tag_value , FALSE);
        }

        /* Error encountered while fetching the value */
        if( result ==0) {
                spprintf(&msg , 0, "locale_get_%s : unable to get locale %s", tag_name , tag_name );
                intl_error_set( NULL, status, msg , 1 TSRMLS_CC );
                efree(msg);
                RETURN_NULL();
        }

}
/* }}} */

/* {{{ proto static string Locale::getScript($locale) 
 * gets the script for the $locale 
 }}} */
/* {{{ proto static string locale_get_script($locale) 
 * gets the script for the $locale 
 */
PHP_FUNCTION( locale_get_script ) 
{
        get_icu_value_src_php( LOC_SCRIPT_TAG , INTERNAL_FUNCTION_PARAM_PASSTHRU );
}
/* }}} */

/* {{{ proto static string Locale::getRegion($locale) 
 * gets the region for the $locale 
 }}} */
/* {{{ proto static string locale_get_region($locale) 
 * gets the region for the $locale 
 */
PHP_FUNCTION( locale_get_region ) 
{
        get_icu_value_src_php( LOC_REGION_TAG , INTERNAL_FUNCTION_PARAM_PASSTHRU );
}
/* }}} */

/* {{{ proto static string Locale::getPrimaryLanguage($locale) 
 * gets the primary language for the $locale 
 }}} */
/* {{{ proto static string locale_get_primary_language($locale) 
 * gets the primary language for the $locale 
 */
PHP_FUNCTION(locale_get_primary_language ) 
{
        get_icu_value_src_php( LOC_LANG_TAG , INTERNAL_FUNCTION_PARAM_PASSTHRU );
}
/* }}} */


/* {{{
 * common code shared by display_xyz functions to  get the value from ICU 
 }}} */
static void get_icu_disp_value_src_php( char* tag_name, INTERNAL_FUNCTION_PARAMETERS) 
{
        const char* loc_name            = NULL;
        int         loc_name_len        = 0;

        const char* disp_loc_name       = NULL;
        int         disp_loc_name_len   = 0;
        int         free_loc_name       = 0;

        UChar*      disp_name           = NULL;
        int32_t     disp_name_len       = 0;

        char*       mod_loc_name        = NULL;

        int32_t     buflen              = 512;
        UErrorCode  status              = U_ZERO_ERROR;

        char*       utf8value           = NULL;
        int         utf8value_len       = 0;

        char*       msg                 = NULL;
        int         grOffset            = 0;

        intl_error_reset( NULL TSRMLS_CC );

        if(zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "s|s",
                &loc_name, &loc_name_len , 
                &disp_loc_name ,&disp_loc_name_len ) == FAILURE)
        {
                spprintf(&msg , 0, "locale_get_display_%s : unable to parse input params", tag_name );
                intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR,  msg , 1 TSRMLS_CC );
                efree(msg);
                RETURN_FALSE;
        }

    if(loc_name_len > ULOC_FULLNAME_CAPACITY) {
        /* See bug 67397: overlong locale names cause trouble in uloc_getDisplayName */
                spprintf(&msg , 0, "locale_get_display_%s : name too long", tag_name );
                intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR,  msg , 1 TSRMLS_CC );
                efree(msg);
                RETURN_FALSE;
    }

        if(loc_name_len == 0) {
                loc_name = intl_locale_get_default(TSRMLS_C);
        }

        if( strcmp(tag_name, DISP_NAME) != 0 ){
                /* Handle grandfathered languages */
                grOffset = findOffset( LOC_GRANDFATHERED , loc_name );
                if( grOffset >= 0 ){
                        if( strcmp(tag_name , LOC_LANG_TAG)==0 ){
                                mod_loc_name = getPreferredTag( loc_name );
                        } else {
                                /* Since Grandfathered, no value, do nothing, retutn NULL */
                                RETURN_FALSE;
                        }
                }
        } /* end of if != LOC_CANONICAL_TAG */

        if( mod_loc_name==NULL ){
                mod_loc_name = estrdup( loc_name );
        }
        
        /* Check if disp_loc_name passed , if not use default locale */
        if( !disp_loc_name){
                disp_loc_name = estrdup(intl_locale_get_default(TSRMLS_C));
                free_loc_name = 1;
        }

    /* Get the disp_value for the given locale */
    do{
        disp_name = erealloc( disp_name , buflen * sizeof(UChar)  );
        disp_name_len = buflen;

                if( strcmp(tag_name , LOC_LANG_TAG)==0 ){
                        buflen = uloc_getDisplayLanguage ( mod_loc_name , disp_loc_name , disp_name , disp_name_len , &status);
                } else if( strcmp(tag_name , LOC_SCRIPT_TAG)==0 ){
                        buflen = uloc_getDisplayScript ( mod_loc_name , disp_loc_name , disp_name , disp_name_len , &status);
                } else if( strcmp(tag_name , LOC_REGION_TAG)==0 ){
                        buflen = uloc_getDisplayCountry ( mod_loc_name , disp_loc_name , disp_name , disp_name_len , &status);
                } else if( strcmp(tag_name , LOC_VARIANT_TAG)==0 ){
                        buflen = uloc_getDisplayVariant ( mod_loc_name , disp_loc_name , disp_name , disp_name_len , &status);
                } else if( strcmp(tag_name , DISP_NAME)==0 ){
                        buflen = uloc_getDisplayName ( mod_loc_name , disp_loc_name , disp_name , disp_name_len , &status);
                }

                /* U_STRING_NOT_TERMINATED_WARNING is admissible here; don't look for it */
                if( U_FAILURE( status ) )
                {
                        if( status == U_BUFFER_OVERFLOW_ERROR )
                        {
                                status = U_ZERO_ERROR;
                                continue;
                        }

                        spprintf(&msg, 0, "locale_get_display_%s : unable to get locale %s", tag_name , tag_name );
                        intl_error_set( NULL, status, msg , 1 TSRMLS_CC );
                        efree(msg);
                        if( disp_name){
                                efree( disp_name );
                        }
                        if( mod_loc_name){
                                efree( mod_loc_name );
                        }
                        if (free_loc_name) {
                                efree((void *)disp_loc_name);
                                disp_loc_name = NULL;
                        }
                        RETURN_FALSE;
                }
        } while( buflen > disp_name_len );

        if( mod_loc_name){
                efree( mod_loc_name );
        }
        if (free_loc_name) {
                efree((void *)disp_loc_name);
                disp_loc_name = NULL;
        }
        /* Convert display locale name from UTF-16 to UTF-8. */
        intl_convert_utf16_to_utf8( &utf8value, &utf8value_len, disp_name, buflen, &status );
        efree( disp_name );
        if( U_FAILURE( status ) )
        {
                spprintf(&msg, 0, "locale_get_display_%s :error converting display name for %s to UTF-8", tag_name , tag_name );
                intl_error_set( NULL, status, msg , 1 TSRMLS_CC );
                efree(msg);
                RETURN_FALSE;
        }

        RETVAL_STRINGL( utf8value, utf8value_len , FALSE);

}
/* }}} */

/* {{{ proto static string Locale::getDisplayName($locale[, $in_locale = null])
* gets the name for the $locale in $in_locale or default_locale
 }}} */
/* {{{ proto static string get_display_name($locale[, $in_locale = null])
* gets the name for the $locale in $in_locale or default_locale
*/
PHP_FUNCTION(locale_get_display_name) 
{
    get_icu_disp_value_src_php( DISP_NAME , INTERNAL_FUNCTION_PARAM_PASSTHRU );
}
/* }}} */

/* {{{ proto static string Locale::getDisplayLanguage($locale[, $in_locale = null])
* gets the language for the $locale in $in_locale or default_locale
 }}} */
/* {{{ proto static string get_display_language($locale[, $in_locale = null])
* gets the language for the $locale in $in_locale or default_locale
*/
PHP_FUNCTION(locale_get_display_language) 
{
    get_icu_disp_value_src_php( LOC_LANG_TAG , INTERNAL_FUNCTION_PARAM_PASSTHRU );
}
/* }}} */

/* {{{ proto static string Locale::getDisplayScript($locale, $in_locale = null)
* gets the script for the $locale in $in_locale or default_locale
 }}} */
/* {{{ proto static string get_display_script($locale, $in_locale = null)
* gets the script for the $locale in $in_locale or default_locale
*/
PHP_FUNCTION(locale_get_display_script) 
{
    get_icu_disp_value_src_php( LOC_SCRIPT_TAG , INTERNAL_FUNCTION_PARAM_PASSTHRU );
}
/* }}} */

/* {{{ proto static string Locale::getDisplayRegion($locale, $in_locale = null)
* gets the region for the $locale in $in_locale or default_locale
 }}} */
/* {{{ proto static string get_display_region($locale, $in_locale = null)
* gets the region for the $locale in $in_locale or default_locale
*/
PHP_FUNCTION(locale_get_display_region) 
{
    get_icu_disp_value_src_php( LOC_REGION_TAG , INTERNAL_FUNCTION_PARAM_PASSTHRU );
}
/* }}} */

/* {{{
* proto static string Locale::getDisplayVariant($locale, $in_locale = null)
* gets the variant for the $locale in $in_locale or default_locale
 }}} */
/* {{{
* proto static string get_display_variant($locale, $in_locale = null)
* gets the variant for the $locale in $in_locale or default_locale
*/
PHP_FUNCTION(locale_get_display_variant) 
{
    get_icu_disp_value_src_php( LOC_VARIANT_TAG , INTERNAL_FUNCTION_PARAM_PASSTHRU );
}
/* }}} */

 /* {{{ proto static array getKeywords(string $locale) {
 * return an associative array containing keyword-value
 * pairs for this locale. The keys are keys to the array (doh!)
 * }}}*/
 /* {{{ proto static array locale_get_keywords(string $locale) {
 * return an associative array containing keyword-value
 * pairs for this locale. The keys are keys to the array (doh!)
 */ 
PHP_FUNCTION( locale_get_keywords )
{
    UEnumeration*   e        = NULL;
    UErrorCode      status   = U_ZERO_ERROR;

    const char*         kw_key        = NULL;
    int32_t         kw_key_len    = 0;

    const char*         loc_name        = NULL;
    int                 loc_name_len    = 0;

/* 
        ICU expects the buffer to be allocated  before calling the function 
        and so the buffer size has been explicitly specified 
        ICU uloc.h #define      ULOC_KEYWORD_AND_VALUES_CAPACITY   100 
        hence the kw_value buffer size is 100
*/
        char*           kw_value        = NULL;
    int32_t     kw_value_len    = 100;

    intl_error_reset( NULL TSRMLS_CC );

    if(zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "s",
        &loc_name, &loc_name_len ) == FAILURE)
    {
        intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR,
             "locale_get_keywords: unable to parse input params", 0 TSRMLS_CC );

        RETURN_FALSE;
    }

    if(loc_name_len == 0) {
        loc_name = intl_locale_get_default(TSRMLS_C);
    }

        /* Get the keywords */
    e = uloc_openKeywords( loc_name, &status );
    if( e != NULL )
    {
                /* Traverse it, filling the return array. */
        array_init( return_value );

        while( ( kw_key = uenum_next( e, &kw_key_len, &status ) ) != NULL ){
                        kw_value = ecalloc( 1 , kw_value_len  );

                        /* Get the keyword value for each keyword */
                        kw_value_len=uloc_getKeywordValue( loc_name,kw_key, kw_value, kw_value_len ,  &status );
                        if (status == U_BUFFER_OVERFLOW_ERROR) {
                                status = U_ZERO_ERROR;
                                kw_value = erealloc( kw_value , kw_value_len+1);
                                kw_value_len=uloc_getKeywordValue( loc_name,kw_key, kw_value, kw_value_len+1 ,  &status );
                        } else if(!U_FAILURE(status)) {
                                kw_value = erealloc( kw_value , kw_value_len+1);
                        } 
                        if (U_FAILURE(status)) {
                                intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR, "locale_get_keywords: Error encountered while getting the keyword  value for the  keyword", 0 TSRMLS_CC );
                                if( kw_value){
                                        efree( kw_value );
                                }
                                zval_dtor(return_value);
                        RETURN_FALSE;
                        }

                add_assoc_stringl( return_value, (char *)kw_key, kw_value , kw_value_len, 0);
                } /* end of while */

        } /* end of if e!=NULL */

    uenum_close( e );
}
/* }}} */

 /* {{{ proto static string Locale::canonicalize($locale) 
 * @return string the canonicalized locale 
 * }}} */
 /* {{{ proto static string locale_canonicalize(Locale $loc, string $locale) 
 * @param string $locale        The locale string to canonicalize
 */
PHP_FUNCTION(locale_canonicalize)
{
        get_icu_value_src_php( LOC_CANONICALIZE_TAG , INTERNAL_FUNCTION_PARAM_PASSTHRU );
}
/* }}} */

/* {{{ append_key_value 
* Internal function which is called from locale_compose
* gets the value for the key_name and appends to the loc_name
* returns 1 if successful , -1 if not found , 
* 0 if array element is not a string , -2 if buffer-overflow
*/
static int append_key_value(smart_str* loc_name, HashTable* hash_arr, char* key_name)
{
        zval**  ele_value       = NULL;

        if(zend_hash_find(hash_arr , key_name , strlen(key_name) + 1 ,(void **)&ele_value ) == SUCCESS ) {
                if(Z_TYPE_PP(ele_value)!= IS_STRING ){
                        /* element value is not a string */
                        return FAILURE;
                }
                if(strcmp(key_name, LOC_LANG_TAG) != 0 && 
                   strcmp(key_name, LOC_GRANDFATHERED_LANG_TAG)!=0 ) {
                        /* not lang or grandfathered tag */
                        smart_str_appendl(loc_name, SEPARATOR , sizeof(SEPARATOR)-1);
                }
                smart_str_appendl(loc_name, Z_STRVAL_PP(ele_value) , Z_STRLEN_PP(ele_value));
                return SUCCESS;
        }

        return LOC_NOT_FOUND;
}
/* }}} */

/* {{{ append_prefix , appends the prefix needed
* e.g. private adds 'x'
*/
static void add_prefix(smart_str* loc_name, char* key_name)
{
        if( strncmp(key_name , LOC_PRIVATE_TAG , 7) == 0 ){
                smart_str_appendl(loc_name, SEPARATOR , sizeof(SEPARATOR)-1);
                smart_str_appendl(loc_name, PRIVATE_PREFIX , sizeof(PRIVATE_PREFIX)-1);
        }
}
/* }}} */

/* {{{ append_multiple_key_values 
* Internal function which is called from locale_compose
* gets the multiple values for the key_name and appends to the loc_name
* used for 'variant','extlang','private' 
* returns 1 if successful , -1 if not found , 
* 0 if array element is not a string , -2 if buffer-overflow
*/
static int append_multiple_key_values(smart_str* loc_name, HashTable* hash_arr, char* key_name TSRMLS_DC)
{
        zval**  ele_value       = NULL;
        int     i               = 0;
        int     isFirstSubtag   = 0;
        int     max_value       = 0;

        /* Variant/ Extlang/Private etc. */
        if( zend_hash_find( hash_arr , key_name , strlen(key_name) + 1 ,(void **)&ele_value ) == SUCCESS ) {
                if( Z_TYPE_PP(ele_value) == IS_STRING ){
                        add_prefix( loc_name , key_name);

                        smart_str_appendl(loc_name, SEPARATOR , sizeof(SEPARATOR)-1);
                        smart_str_appendl(loc_name, Z_STRVAL_PP(ele_value) , Z_STRLEN_PP(ele_value));
                        return SUCCESS;
                } else if(Z_TYPE_PP(ele_value) == IS_ARRAY ) {
                        HashPosition pos;
                        HashTable *arr = HASH_OF(*ele_value);
                        zval **data = NULL;

                        zend_hash_internal_pointer_reset_ex(arr, &pos);
                        while(zend_hash_get_current_data_ex(arr, (void **)&data, &pos) != FAILURE) {
                                if(Z_TYPE_PP(data) != IS_STRING) {
                                        return FAILURE;
                                }
                                if (isFirstSubtag++ == 0){
                                        add_prefix(loc_name , key_name);
                                }
                                smart_str_appendl(loc_name, SEPARATOR , sizeof(SEPARATOR)-1);
                                smart_str_appendl(loc_name, Z_STRVAL_PP(data) , Z_STRLEN_PP(data));
                                zend_hash_move_forward_ex(arr, &pos);
                        }
                        return SUCCESS;
                } else {
                        return FAILURE;
                }
        } else {
                char cur_key_name[31];
                /* Decide the max_value: the max. no. of elements allowed */
                if( strcmp(key_name , LOC_VARIANT_TAG) ==0 ){
                        max_value  = MAX_NO_VARIANT;
                }
                if( strcmp(key_name , LOC_EXTLANG_TAG) ==0 ){
                        max_value  = MAX_NO_EXTLANG;
                }
                if( strcmp(key_name , LOC_PRIVATE_TAG) ==0 ){
                        max_value  = MAX_NO_PRIVATE;
                }

                /* Multiple variant values as variant0, variant1 ,variant2 */
                isFirstSubtag = 0;
                for( i=0 ; i< max_value; i++ ){  
                        snprintf( cur_key_name , 30, "%s%d", key_name , i);     
                        if( zend_hash_find( hash_arr , cur_key_name , strlen(cur_key_name) + 1,(void **)&ele_value ) == SUCCESS ){
                                if( Z_TYPE_PP(ele_value)!= IS_STRING ){
                                        /* variant is not a string */
                                        return FAILURE;
                                }
                                /* Add the contents */
                                if (isFirstSubtag++ == 0){
                                        add_prefix(loc_name , cur_key_name);
                                }
                                smart_str_appendl(loc_name, SEPARATOR , sizeof(SEPARATOR)-1);
                                smart_str_appendl(loc_name, Z_STRVAL_PP(ele_value) , Z_STRLEN_PP(ele_value));
                        }
                } /* end of for */
        } /* end of else */

        return SUCCESS;
}
/* }}} */

/*{{{
* If applicable sets error message and aborts locale_compose gracefully
* returns 0  if locale_compose needs to be aborted 
* otherwise returns 1
*/
static int handleAppendResult( int result, smart_str* loc_name TSRMLS_DC)
{
        intl_error_reset( NULL TSRMLS_CC );
        if( result == FAILURE) {
                intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR,
                         "locale_compose: parameter array element is not a string", 0 TSRMLS_CC );
                smart_str_free(loc_name);
                return 0;
        }
        return 1;
}
/* }}} */

#define RETURN_SMART_STR(s) smart_str_0((s)); RETURN_STRINGL((s)->c, (s)->len, 0)
/* {{{ proto static string Locale::composeLocale($array) 
* Creates a locale by combining the parts of locale-ID passed   
* }}} */
/* {{{ proto static string compose_locale($array) 
* Creates a locale by combining the parts of locale-ID passed   
* }}} */
PHP_FUNCTION(locale_compose)
{
        smart_str       loc_name_s = {0};
        smart_str *loc_name = &loc_name_s;
        zval*                   arr     = NULL;
        HashTable*              hash_arr = NULL;
        int                     result = 0;

        intl_error_reset( NULL TSRMLS_CC );

        if(zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "a",
                &arr) == FAILURE)
        {
                intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR,
                         "locale_compose: unable to parse input params", 0 TSRMLS_CC );
                RETURN_FALSE;
        }

        hash_arr = HASH_OF( arr );

        if( !hash_arr || zend_hash_num_elements( hash_arr ) == 0 )
                RETURN_FALSE;

        /* Check for grandfathered first */
        result = append_key_value(loc_name, hash_arr,  LOC_GRANDFATHERED_LANG_TAG);     
        if( result == SUCCESS){
                RETURN_SMART_STR(loc_name);
        }
        if( !handleAppendResult( result, loc_name TSRMLS_CC)){
                RETURN_FALSE;
        }

        /* Not grandfathered */
        result = append_key_value(loc_name, hash_arr , LOC_LANG_TAG);   
        if( result == LOC_NOT_FOUND ){
                intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR,
                "locale_compose: parameter array does not contain 'language' tag.", 0 TSRMLS_CC );
                smart_str_free(loc_name);
                RETURN_FALSE;
        }
        if( !handleAppendResult( result, loc_name TSRMLS_CC)){
                RETURN_FALSE;
        }

        /* Extlang */
        result = append_multiple_key_values(loc_name, hash_arr , LOC_EXTLANG_TAG TSRMLS_CC);
        if( !handleAppendResult( result, loc_name TSRMLS_CC)){
                RETURN_FALSE;
        }

        /* Script */
        result = append_key_value(loc_name, hash_arr , LOC_SCRIPT_TAG); 
        if( !handleAppendResult( result, loc_name TSRMLS_CC)){
                RETURN_FALSE;
        }
        
        /* Region */
        result = append_key_value( loc_name, hash_arr , LOC_REGION_TAG);
        if( !handleAppendResult( result, loc_name TSRMLS_CC)){
                RETURN_FALSE;
        }

        /* Variant */
        result = append_multiple_key_values( loc_name, hash_arr , LOC_VARIANT_TAG TSRMLS_CC); 
        if( !handleAppendResult( result, loc_name TSRMLS_CC)){
                RETURN_FALSE;
        }

        /* Private */
        result = append_multiple_key_values( loc_name, hash_arr , LOC_PRIVATE_TAG TSRMLS_CC);
        if( !handleAppendResult( result, loc_name TSRMLS_CC)){
                RETURN_FALSE;
        }

        RETURN_SMART_STR(loc_name);
}
/* }}} */


/*{{{
* Parses the locale and returns private subtags  if existing
* else returns NULL
* e.g. for locale='en_US-x-prv1-prv2-prv3'
* returns a pointer to the string 'prv1-prv2-prv3'
*/
static char* get_private_subtags(const char* loc_name)
{
        char*   result =NULL;
        int     singletonPos = 0;
        int     len =0; 
        const char*     mod_loc_name =NULL;

        if( loc_name && (len = strlen(loc_name)>0 ) ){
                mod_loc_name = loc_name ; 
                len   = strlen(mod_loc_name);
                while( (singletonPos = getSingletonPos(mod_loc_name))!= -1){

                        if( singletonPos!=-1){ 
                                if( (*(mod_loc_name+singletonPos)=='x') || (*(mod_loc_name+singletonPos)=='X') ){               
                                        /* private subtag start found */
                                        if( singletonPos + 2 ==  len){
                                                /* loc_name ends with '-x-' ; return  NULL */
                                        }
                                        else{
                                                /* result = mod_loc_name + singletonPos +2; */
                                                result = estrndup(mod_loc_name + singletonPos+2  , (len -( singletonPos +2) ) );
                                        }
                                        break;
                                }
                                else{
                                        if( singletonPos + 1 >=  len){
                                                /* String end */
                                                break;
                                        } else {
                                                /* singleton found but not a private subtag , hence check further in the string for the private subtag */
                                                mod_loc_name = mod_loc_name + singletonPos +1;
                                                len = strlen(mod_loc_name);
                                        }
                                }
                        }

                } /* end of while */
        }
        
        return result;
}
/* }}} */

/* {{{ code used by locale_parse
*/
static int add_array_entry(const char* loc_name, zval* hash_arr, char* key_name TSRMLS_DC)
{
        char*   key_value       = NULL;
        char*   cur_key_name    = NULL;
        char*   token           = NULL;
        char*   last_ptr        = NULL;

        int     result          = 0;
        int     cur_result      = 0;
        int     cnt             = 0;


        if( strcmp(key_name , LOC_PRIVATE_TAG)==0 ){
                key_value = get_private_subtags( loc_name );
                result = 1;
        } else {
                key_value = get_icu_value_internal( loc_name , key_name , &result,1 );
        }
        if( (strcmp(key_name , LOC_PRIVATE_TAG)==0) || 
                ( strcmp(key_name , LOC_VARIANT_TAG)==0) ){
                if( result > 0 && key_value){
                        /* Tokenize on the "_" or "-"  */
                        token = php_strtok_r( key_value , DELIMITER ,&last_ptr);        
                        if( cur_key_name ){
                                efree( cur_key_name);
                        }
                        cur_key_name = (char*)ecalloc( 25,  25);
                        sprintf( cur_key_name , "%s%d", key_name , cnt++);      
                        add_assoc_string( hash_arr, cur_key_name , token ,TRUE );
                        /* tokenize on the "_" or "-" and stop  at singleton if any */
                        while( (token = php_strtok_r(NULL , DELIMITER , &last_ptr)) && (strlen(token)>1) ){
                                sprintf( cur_key_name , "%s%d", key_name , cnt++);      
                                add_assoc_string( hash_arr, cur_key_name , token , TRUE );
                        }
/*
                        if( strcmp(key_name, LOC_PRIVATE_TAG) == 0 ){
                        }
*/
                }
        } else {
                if( result == 1 ){
                        add_assoc_string( hash_arr, key_name , key_value , TRUE );
                        cur_result = 1;
                }
        }

        if( cur_key_name ){
                efree( cur_key_name);
        }
        /*if( key_name != LOC_PRIVATE_TAG && key_value){*/
        if( key_value){
                efree(key_value);       
        }
        return cur_result;
}
/* }}} */

/* {{{ proto static array Locale::parseLocale($locale) 
* parses a locale-id into an array the different parts of it
 }}} */
/* {{{ proto static array parse_locale($locale) 
* parses a locale-id into an array the different parts of it
*/
PHP_FUNCTION(locale_parse)
{
    const char* loc_name        = NULL;
    int         loc_name_len    = 0;
    int         grOffset        = 0;

    intl_error_reset( NULL TSRMLS_CC );

    if(zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "s",
        &loc_name, &loc_name_len ) == FAILURE)
    {
        intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR,
             "locale_parse: unable to parse input params", 0 TSRMLS_CC );

        RETURN_FALSE;
    }

    if(loc_name_len == 0) {
        loc_name = intl_locale_get_default(TSRMLS_C);
    }

        array_init( return_value );

        grOffset =  findOffset( LOC_GRANDFATHERED , loc_name );
        if( grOffset >= 0 ){
                add_assoc_string( return_value , LOC_GRANDFATHERED_LANG_TAG , estrdup(loc_name) ,FALSE );
        }
        else{
                /* Not grandfathered */
                add_array_entry( loc_name , return_value , LOC_LANG_TAG TSRMLS_CC);
                add_array_entry( loc_name , return_value , LOC_SCRIPT_TAG TSRMLS_CC);
                add_array_entry( loc_name , return_value , LOC_REGION_TAG TSRMLS_CC);
                add_array_entry( loc_name , return_value , LOC_VARIANT_TAG TSRMLS_CC);
                add_array_entry( loc_name , return_value , LOC_PRIVATE_TAG TSRMLS_CC);
        }
}
/* }}} */

/* {{{ proto static array Locale::getAllVariants($locale)
* gets an array containing the list of variants, or null
 }}} */
/* {{{ proto static array locale_get_all_variants($locale)
* gets an array containing the list of variants, or null
*/
PHP_FUNCTION(locale_get_all_variants)
{
        const char*     loc_name        = NULL;
        int             loc_name_len    = 0;

        int     result          = 0;
        char*   token           = NULL;
        char*   variant         = NULL;
        char*   saved_ptr       = NULL;

        intl_error_reset( NULL TSRMLS_CC );
        
        if(zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "s",
        &loc_name, &loc_name_len ) == FAILURE)
        {
                intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR,
             "locale_parse: unable to parse input params", 0 TSRMLS_CC );

                RETURN_FALSE;
        }

        if(loc_name_len == 0) {
                loc_name = intl_locale_get_default(TSRMLS_C);
        }


        array_init( return_value );

        /* If the locale is grandfathered, stop, no variants */
        if( findOffset( LOC_GRANDFATHERED , loc_name ) >=  0 ){ 
                /* ("Grandfathered Tag. No variants."); */
        }
        else {  
        /* Call ICU variant */
                variant = get_icu_value_internal( loc_name , LOC_VARIANT_TAG , &result ,0);
                if( result > 0 && variant){
                        /* Tokenize on the "_" or "-" */
                        token = php_strtok_r( variant , DELIMITER , &saved_ptr);        
                        add_next_index_stringl( return_value, token , strlen(token) ,TRUE );
                        /* tokenize on the "_" or "-" and stop  at singleton if any     */
                        while( (token = php_strtok_r(NULL , DELIMITER, &saved_ptr)) && (strlen(token)>1) ){
                                add_next_index_stringl( return_value, token , strlen(token) ,TRUE );
                        }
                }
                if( variant ){
                        efree( variant );
                }
        }
                        

}
/* }}} */

/*{{{
* Converts to lower case and also replaces all hyphens with the underscore
*/
static int strToMatch(const char* str ,char *retstr)
{
        char*   anchor  = NULL;
        const char*     anchor1 = NULL;
        int     result  = 0;

    if( (!str) || str[0] == '\0'){
        return result;
    } else {
        anchor = retstr;
        anchor1 = str;
        while( (*str)!='\0' ){
                if( *str == '-' ){
                        *retstr =  '_';
                } else {
                        *retstr = tolower(*str);
                }
            str++;
            retstr++;
        }
        *retstr = '\0';
        retstr=  anchor;
        str=  anchor1;
        result = 1;
    }

    return(result);
}
/* }}} */

/* {{{ proto static boolean Locale::filterMatches(string $langtag, string $locale[, bool $canonicalize])
* Checks if a $langtag filter matches with $locale according to RFC 4647's basic filtering algorithm 
*/
/* }}} */
/* {{{ proto boolean locale_filter_matches(string $langtag, string $locale[, bool $canonicalize])
* Checks if a $langtag filter matches with $locale according to RFC 4647's basic filtering algorithm 
*/
PHP_FUNCTION(locale_filter_matches)
{
        char*           lang_tag        = NULL;
        int             lang_tag_len    = 0;
        const char*     loc_range       = NULL;
        int             loc_range_len   = 0;

        int             result          = 0;
        char*           token           = 0;
        char*           chrcheck        = NULL;

        char*           can_lang_tag    = NULL;
        char*           can_loc_range   = NULL;

        char*           cur_lang_tag    = NULL;
        char*           cur_loc_range   = NULL;

        zend_bool       boolCanonical   = 0;    
        UErrorCode      status          = U_ZERO_ERROR;

        intl_error_reset( NULL TSRMLS_CC );
        
        if(zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "ss|b",
                &lang_tag, &lang_tag_len , &loc_range , &loc_range_len , 
                &boolCanonical) == FAILURE)
        {
                intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR,
                "locale_filter_matches: unable to parse input params", 0 TSRMLS_CC );

                RETURN_FALSE;
        }

        if(loc_range_len == 0) {
                loc_range = intl_locale_get_default(TSRMLS_C);
        }

        if( strcmp(loc_range,"*")==0){
                RETURN_TRUE;
        }

        if( boolCanonical ){
                /* canonicalize loc_range */
                can_loc_range=get_icu_value_internal( loc_range , LOC_CANONICALIZE_TAG , &result , 0);
                if( result ==0) {
                        intl_error_set( NULL, status, 
                                "locale_filter_matches : unable to canonicalize loc_range" , 0 TSRMLS_CC );
                        RETURN_FALSE;
                }

                /* canonicalize lang_tag */
                can_lang_tag = get_icu_value_internal( lang_tag , LOC_CANONICALIZE_TAG , &result ,  0);
                if( result ==0) {
                        intl_error_set( NULL, status, 
                                "locale_filter_matches : unable to canonicalize lang_tag" , 0 TSRMLS_CC );
                        RETURN_FALSE;
                }

                /* Convert to lower case for case-insensitive comparison */
                cur_lang_tag = ecalloc( 1, strlen(can_lang_tag) + 1);

                /* Convert to lower case for case-insensitive comparison */
                result = strToMatch( can_lang_tag , cur_lang_tag);
                if( result == 0) {
                        efree( cur_lang_tag );
                        efree( can_lang_tag );
                        RETURN_FALSE;
                }

                cur_loc_range = ecalloc( 1, strlen(can_loc_range) + 1);
                result = strToMatch( can_loc_range , cur_loc_range );
                if( result == 0) {
                        efree( cur_lang_tag );
                        efree( can_lang_tag );
                        efree( cur_loc_range );
                        efree( can_loc_range );
                        RETURN_FALSE;
                }

                /* check if prefix */
                token   = strstr( cur_lang_tag , cur_loc_range );
        
                if( token && (token==cur_lang_tag) ){
                        /* check if the char. after match is SEPARATOR */
                        chrcheck = token + (strlen(cur_loc_range));
                        if( isIDSeparator(*chrcheck) || isEndOfTag(*chrcheck) ){ 
                                if( cur_lang_tag){
                                        efree( cur_lang_tag );
                                }
                                if( cur_loc_range){
                                        efree( cur_loc_range );
                                }
                                if( can_lang_tag){
                                        efree( can_lang_tag );
                                }
                                if( can_loc_range){
                                        efree( can_loc_range );
                                }
                                RETURN_TRUE;
                        }
                }

                /* No prefix as loc_range */
                if( cur_lang_tag){
                        efree( cur_lang_tag );
                }
                if( cur_loc_range){
                        efree( cur_loc_range );
                }
                if( can_lang_tag){
                        efree( can_lang_tag );
                }
                if( can_loc_range){
                        efree( can_loc_range );
                }
                RETURN_FALSE;

        } /* end of if isCanonical */
        else{
                /* Convert to lower case for case-insensitive comparison */
                cur_lang_tag = ecalloc( 1, strlen(lang_tag ) + 1);
                
                result = strToMatch( lang_tag , cur_lang_tag);
                if( result == 0) {
                        efree( cur_lang_tag );
                        RETURN_FALSE;
                }
                cur_loc_range = ecalloc( 1, strlen(loc_range ) + 1);
                result = strToMatch( loc_range , cur_loc_range );
                if( result == 0) {
                        efree( cur_lang_tag );
                        efree( cur_loc_range );
                        RETURN_FALSE;
                }

                /* check if prefix */
                token   = strstr( cur_lang_tag , cur_loc_range );
                
                if( token && (token==cur_lang_tag) ){
                        /* check if the char. after match is SEPARATOR */
                        chrcheck = token + (strlen(cur_loc_range));
                        if( isIDSeparator(*chrcheck) || isEndOfTag(*chrcheck) ){ 
                                if( cur_lang_tag){
                                        efree( cur_lang_tag );
                                }
                                if( cur_loc_range){
                                        efree( cur_loc_range );
                                }
                                RETURN_TRUE;
                        }
                }

                /* No prefix as loc_range */
                if( cur_lang_tag){
                        efree( cur_lang_tag );
                }
                if( cur_loc_range){
                        efree( cur_loc_range );
                }
                RETURN_FALSE;

        }
}
/* }}} */

static void array_cleanup( char* arr[] , int arr_size)
{
        int i=0;
        for( i=0; i< arr_size; i++ ){ 
                if( arr[i*2] ){
                        efree( arr[i*2]);
                }
        }
        efree(arr);
}

#define LOOKUP_CLEAN_RETURN(value)      array_cleanup(cur_arr, cur_arr_len); return (value)
/* {{{
* returns the lookup result to lookup_loc_range_src_php 
* internal function
*/
static char* lookup_loc_range(const char* loc_range, HashTable* hash_arr, int canonicalize  TSRMLS_DC)
{
        int     i = 0;
        int     cur_arr_len = 0;
        int result = 0;

        char* lang_tag = NULL;
        zval** ele_value = NULL;
        char** cur_arr = NULL;

        char* cur_loc_range     = NULL;
        char* can_loc_range     = NULL;
        int     saved_pos = 0;

        char* return_value = NULL;

        cur_arr = ecalloc(zend_hash_num_elements(hash_arr)*2, sizeof(char *));
        /* convert the array to lowercase , also replace hyphens with the underscore and store it in cur_arr */
        for(zend_hash_internal_pointer_reset(hash_arr);
                zend_hash_has_more_elements(hash_arr) == SUCCESS;
                zend_hash_move_forward(hash_arr)) {
                
                if (zend_hash_get_current_data(hash_arr, (void**)&ele_value) == FAILURE) {
                        /* Should never actually fail since the key is known to exist.*/
                        continue;
                }
                if(Z_TYPE_PP(ele_value)!= IS_STRING) {
                        /* element value is not a string */
                        intl_error_set(NULL, U_ILLEGAL_ARGUMENT_ERROR, "lookup_loc_range: locale array element is not a string", 0 TSRMLS_CC);
                        LOOKUP_CLEAN_RETURN(NULL);
                } 
                cur_arr[cur_arr_len*2] = estrndup(Z_STRVAL_PP(ele_value), Z_STRLEN_PP(ele_value));
                result = strToMatch(Z_STRVAL_PP(ele_value), cur_arr[cur_arr_len*2]);
                if(result == 0) {
                        intl_error_set(NULL, U_ILLEGAL_ARGUMENT_ERROR, "lookup_loc_range: unable to canonicalize lang_tag", 0 TSRMLS_CC);
                        LOOKUP_CLEAN_RETURN(NULL);
                }
                cur_arr[cur_arr_len*2+1] = Z_STRVAL_PP(ele_value);
                cur_arr_len++ ; 
        } /* end of for */

        /* Canonicalize array elements */
        if(canonicalize) {
                for(i=0; i<cur_arr_len; i++) { 
                        lang_tag = get_icu_value_internal(cur_arr[i*2], LOC_CANONICALIZE_TAG, &result, 0);
                        if(result != 1 || lang_tag == NULL || !lang_tag[0]) {
                                if(lang_tag) {
                                        efree(lang_tag);
                                }
                                intl_error_set(NULL, U_ILLEGAL_ARGUMENT_ERROR, "lookup_loc_range: unable to canonicalize lang_tag" , 0 TSRMLS_CC);
                                LOOKUP_CLEAN_RETURN(NULL);
                        }
                        cur_arr[i*2] = erealloc(cur_arr[i*2], strlen(lang_tag)+1);
                        result = strToMatch(lang_tag, cur_arr[i*2]);    
                        efree(lang_tag);
                        if(result == 0) {
                                intl_error_set(NULL, U_ILLEGAL_ARGUMENT_ERROR, "lookup_loc_range: unable to canonicalize lang_tag" , 0 TSRMLS_CC);
                                LOOKUP_CLEAN_RETURN(NULL);
                        }
                }

        }

        if(canonicalize) {
                /* Canonicalize the loc_range */
                can_loc_range = get_icu_value_internal(loc_range, LOC_CANONICALIZE_TAG, &result , 0);
                if( result != 1 || can_loc_range == NULL || !can_loc_range[0]) {
                        /* Error */
                        intl_error_set(NULL, U_ILLEGAL_ARGUMENT_ERROR, "lookup_loc_range: unable to canonicalize loc_range" , 0 TSRMLS_CC );
                        if(can_loc_range) {
                                efree(can_loc_range);
                        }
                        LOOKUP_CLEAN_RETURN(NULL);
                } else {
                        loc_range = can_loc_range;
                }
        } 

        cur_loc_range = ecalloc(1, strlen(loc_range)+1);
        /* convert to lower and replace hyphens */
        result = strToMatch(loc_range, cur_loc_range);  
        if(can_loc_range) {
                efree(can_loc_range);
        }
        if(result == 0) {
                intl_error_set(NULL, U_ILLEGAL_ARGUMENT_ERROR, "lookup_loc_range: unable to canonicalize lang_tag" , 0 TSRMLS_CC);
                LOOKUP_CLEAN_RETURN(NULL);
        }

        /* Lookup for the lang_tag match */
        saved_pos = strlen(cur_loc_range);
        while(saved_pos > 0) {
                for(i=0; i< cur_arr_len; i++){ 
                        if(cur_arr[i*2] != NULL && strlen(cur_arr[i*2]) == saved_pos && strncmp(cur_loc_range, cur_arr[i*2], saved_pos) == 0) { 
                                /* Match found */
                                return_value = estrdup(canonicalize?cur_arr[i*2]:cur_arr[i*2+1]);
                                efree(cur_loc_range);
                                LOOKUP_CLEAN_RETURN(return_value);
                        }
                }
                saved_pos = getStrrtokenPos(cur_loc_range, saved_pos);
        }

        /* Match not found */
        efree(cur_loc_range);
        LOOKUP_CLEAN_RETURN(NULL);
}
/* }}} */

/* {{{ proto string Locale::lookup(array $langtag, string $locale[, bool $canonicalize[, string $default = null]]) 
* Searchs the items in $langtag for the best match to the language
* range 
*/
/* }}} */
/* {{{ proto string locale_lookup(array $langtag, string $locale[, bool $canonicalize[, string $default = null]])
* Searchs the items in $langtag for the best match to the language
* range 
*/
PHP_FUNCTION(locale_lookup)
{
        char*           fallback_loc            = NULL;
        int             fallback_loc_len        = 0;
        const char*     loc_range               = NULL;
        int             loc_range_len           = 0;

        zval*           arr                             = NULL;
        HashTable*      hash_arr                = NULL;
        zend_bool       boolCanonical   = 0;
        char*           result                  =NULL;

        intl_error_reset( NULL TSRMLS_CC );

        if(zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "as|bs", &arr, &loc_range, &loc_range_len,
                &boolCanonical, &fallback_loc, &fallback_loc_len) == FAILURE) {
                intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR, "locale_lookup: unable to parse input params", 0 TSRMLS_CC );
                RETURN_FALSE;
        }

        if(loc_range_len == 0) {
                loc_range = intl_locale_get_default(TSRMLS_C);
        }

        hash_arr = HASH_OF(arr);

        if( !hash_arr || zend_hash_num_elements( hash_arr ) == 0 ) {
                RETURN_EMPTY_STRING();
        } 
        
        result = lookup_loc_range(loc_range, hash_arr, boolCanonical TSRMLS_CC);
        if(result == NULL || result[0] == '\0') {
                if( fallback_loc ) {
                        result = estrndup(fallback_loc, fallback_loc_len);
                } else {
                        RETURN_EMPTY_STRING();
                }
        }

        RETVAL_STRINGL(result, strlen(result), 0);
}
/* }}} */

/* {{{ proto string Locale::acceptFromHttp(string $http_accept)
* Tries to find out best available locale based on HTTP �Accept-Language� header
*/
/* }}} */
/* {{{ proto string locale_accept_from_http(string $http_accept)
* Tries to find out best available locale based on HTTP �Accept-Language� header
*/
PHP_FUNCTION(locale_accept_from_http)
{
        UEnumeration *available;
        char *http_accept = NULL;
        int http_accept_len;
        UErrorCode status = 0;
        int len;
        char resultLocale[INTL_MAX_LOCALE_LEN+1];
        UAcceptResult outResult;

        if(zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "s", &http_accept, &http_accept_len) == FAILURE)
        {
                intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR,
                "locale_accept_from_http: unable to parse input parameters", 0 TSRMLS_CC );
                RETURN_FALSE;
        }
        
        available = ures_openAvailableLocales(NULL, &status);
        INTL_CHECK_STATUS(status, "locale_accept_from_http: failed to retrieve locale list");
        len = uloc_acceptLanguageFromHTTP(resultLocale, INTL_MAX_LOCALE_LEN, 
                                                &outResult, http_accept, available, &status);
        uenum_close(available);
        INTL_CHECK_STATUS(status, "locale_accept_from_http: failed to find acceptable locale");
        if (len < 0 || outResult == ULOC_ACCEPT_FAILED) {
                RETURN_FALSE;
        }
        RETURN_STRINGL(resultLocale, len, 1);
}
/* }}} */

/*
 * Local variables:
 * tab-width: 4
 * c-basic-offset: 4
 * End:
 * vim600: noet sw=4 ts=4 fdm=marker
 * vim<600: noet sw=4 ts=4
 *can_loc_len
*/

/* [<][>][^][v][top][bottom][index][help] */