1/*
2   +----------------------------------------------------------------------+
3   | PHP Version 5                                                        |
4   +----------------------------------------------------------------------+
5   | This source file is subject to version 3.01 of the PHP license,      |
6   | that is bundled with this package in the file LICENSE, and is        |
7   | available through the world-wide-web at the following url:           |
8   | http://www.php.net/license/3_01.txt                                  |
9   | If you did not receive a copy of the PHP license and are unable to   |
10   | obtain it through the world-wide-web, please send a note to          |
11   | license@php.net so we can mail you a copy immediately.               |
12   +----------------------------------------------------------------------+
13   | Authors: Kirti Velankar <kirtig@yahoo-inc.com>                       |
14   +----------------------------------------------------------------------+
15*/
16
17/* $Id$ */
18
19#ifdef HAVE_CONFIG_H
20#include "config.h"
21#endif
22
23#include <unicode/ustring.h>
24#include <unicode/udata.h>
25#include <unicode/putil.h>
26#include <unicode/ures.h>
27
28#include "php_intl.h"
29#include "locale.h"
30#include "locale_class.h"
31#include "locale_methods.h"
32#include "intl_convert.h"
33#include "intl_data.h"
34
35#include <zend_API.h>
36#include <zend.h>
37#include <php.h>
38#include "main/php_ini.h"
39#include "ext/standard/php_smart_str.h"
40
41ZEND_EXTERN_MODULE_GLOBALS( intl )
42
43/* Sizes required for the strings "variant15" , "extlang11", "private12" etc. */
44#define SEPARATOR "_"
45#define SEPARATOR1 "-"
46#define DELIMITER "-_"
47#define EXTLANG_PREFIX "a"
48#define PRIVATE_PREFIX "x"
49#define DISP_NAME "name"
50
51#define MAX_NO_VARIANT  15
52#define MAX_NO_EXTLANG  3
53#define MAX_NO_PRIVATE  15
54#define MAX_NO_LOOKUP_LANG_TAG  100
55
56#define LOC_NOT_FOUND 1
57
58/* Sizes required for the strings "variant15" , "extlang3", "private12" etc. */
59#define VARIANT_KEYNAME_LEN  11
60#define EXTLANG_KEYNAME_LEN  10
61#define PRIVATE_KEYNAME_LEN  11
62
63/* Based on IANA registry at the time of writing this code
64*
65*/
66static const char * const LOC_GRANDFATHERED[] = {
67    "art-lojban",       "i-klingon",        "i-lux",            "i-navajo",     "no-bok",       "no-nyn",
68    "cel-gaulish",      "en-GB-oed",        "i-ami",
69    "i-bnn",        "i-default",        "i-enochian",
70    "i-mingo",      "i-pwn",        "i-tao",
71    "i-tay",        "i-tsu",        "sgn-BE-fr",
72    "sgn-BE-nl",        "sgn-CH-de",        "zh-cmn",
73    "zh-cmn-Hans",      "zh-cmn-Hant",      "zh-gan" ,
74    "zh-guoyu",         "zh-hakka",         "zh-min",
75    "zh-min-nan",       "zh-wuu",       "zh-xiang",
76    "zh-yue",       NULL
77};
78
79/* Based on IANA registry at the time of writing this code
80*  This array lists the preferred values for the grandfathered tags if applicable
81*  This is in sync with the array LOC_GRANDFATHERED
82*  e.g. the offsets of the grandfathered tags match the offset of the preferred  value
83*/
84static const int        LOC_PREFERRED_GRANDFATHERED_LEN = 6;
85static const char * const   LOC_PREFERRED_GRANDFATHERED[]  = {
86    "jbo",          "tlh",          "lb",
87    "nv",           "nb",           "nn",
88    NULL
89};
90
91/*returns TRUE if a is an ID separator FALSE otherwise*/
92#define isIDSeparator(a) (a == '_' || a == '-')
93#define isKeywordSeparator(a) (a == '@' )
94#define isEndOfTag(a) (a == '\0' )
95
96#define isPrefixLetter(a) ((a=='x')||(a=='X')||(a=='i')||(a=='I'))
97
98/*returns TRUE if one of the special prefixes is here (s=string)
99  'x-' or 'i-' */
100#define isIDPrefix(s) (isPrefixLetter(s[0])&&isIDSeparator(s[1]))
101#define isKeywordPrefix(s) ( isKeywordSeparator(s[0]) )
102
103/* Dot terminates it because of POSIX form  where dot precedes the codepage
104 * except for variant */
105#define isTerminator(a)  ((a==0)||(a=='.')||(a=='@'))
106
107/* {{{ return the offset of 'key' in the array 'list'.
108 * returns -1 if not present */
109static int16_t findOffset(const char* const* list, const char* key)
110{
111    const char* const* anchor = list;
112    while (*list != NULL) {
113        if (strcmp(key, *list) == 0) {
114            return (int16_t)(list - anchor);
115        }
116        list++;
117    }
118
119    return -1;
120
121}
122/*}}}*/
123
124static char* getPreferredTag(char* gf_tag)
125{
126    char* result = NULL;
127    int grOffset = 0;
128
129    grOffset = findOffset( LOC_GRANDFATHERED ,gf_tag);
130    if( grOffset < LOC_PREFERRED_GRANDFATHERED_LEN ){
131        /* return preferred tag */
132        result = estrdup( LOC_PREFERRED_GRANDFATHERED[grOffset] );
133    } else {
134        /* Return correct grandfathered language tag */
135        result = estrdup( LOC_GRANDFATHERED[grOffset] );
136    }
137    return result;
138}
139
140/* {{{
141* returns the position of next token for lookup
142* or -1 if no token
143* strtokr equivalent search for token in reverse direction
144*/
145static int getStrrtokenPos(char* str, int savedPos)
146{
147    int result =-1;
148    int i;
149
150    for(i=savedPos-1; i>=0; i--) {
151        if(isIDSeparator(*(str+i)) ){
152            /* delimiter found; check for singleton */
153            if(i>=2 && isIDSeparator(*(str+i-2)) ){
154                /* a singleton; so send the position of token before the singleton */
155                result = i-2;
156            } else {
157                result = i;
158            }
159            break;
160        }
161    }
162    if(result < 1){
163        /* Just in case inavlid locale e.g. '-x-xyz' or '-sl_Latn' */
164        result =-1;
165    }
166    return result;
167}
168/* }}} */
169
170/* {{{
171* returns the position of a singleton if present
172* returns -1 if no singleton
173* strtok equivalent search for singleton
174*/
175static int getSingletonPos(char* str)
176{
177    int result =-1;
178    int i=0;
179    int len = 0;
180
181    if( str && ((len=strlen(str))>0) ){
182        for( i=0; i<len ; i++){
183            if( isIDSeparator(*(str+i)) ){
184                if( i==1){
185                    /* string is of the form x-avy or a-prv1 */
186                    result =0;
187                    break;
188                } else {
189                    /* delimiter found; check for singleton */
190                    if( isIDSeparator(*(str+i+2)) ){
191                        /* a singleton; so send the position of separator before singleton */
192                        result = i+1;
193                        break;
194                    }
195                }
196            }
197        }/* end of for */
198
199    }
200    return result;
201}
202/* }}} */
203
204/* {{{ proto static string Locale::getDefault(  )
205   Get default locale */
206/* }}} */
207/* {{{ proto static string locale_get_default( )
208   Get default locale */
209PHP_NAMED_FUNCTION(zif_locale_get_default)
210{
211    RETURN_STRING( intl_locale_get_default( TSRMLS_C ), TRUE );
212}
213
214/* }}} */
215
216/* {{{ proto static string Locale::setDefault( string $locale )
217   Set default locale */
218/* }}} */
219/* {{{ proto static string locale_set_default( string $locale )
220   Set default locale */
221PHP_NAMED_FUNCTION(zif_locale_set_default)
222{
223    char* locale_name = NULL;
224    int   len=0;
225
226    if(zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC,  "s",
227        &locale_name ,&len ) == FAILURE)
228    {
229        intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR,
230                "locale_set_default: unable to parse input params", 0 TSRMLS_CC );
231
232        RETURN_FALSE;
233    }
234
235    if(len == 0) {
236        locale_name =  (char *)uloc_getDefault() ;
237        len = strlen(locale_name);
238    }
239
240    zend_alter_ini_entry(LOCALE_INI_NAME, sizeof(LOCALE_INI_NAME), locale_name, len, PHP_INI_USER, PHP_INI_STAGE_RUNTIME);
241
242    RETURN_TRUE;
243}
244/* }}} */
245
246/* {{{
247* Gets the value from ICU
248* common code shared by get_primary_language,get_script or get_region or get_variant
249* result = 0 if error, 1 if successful , -1 if no value
250*/
251static char* get_icu_value_internal( char* loc_name , char* tag_name, int* result , int fromParseLocale)
252{
253    char*       tag_value   = NULL;
254    int32_t         tag_value_len   = 512;
255
256    int     singletonPos    = 0;
257    char*           mod_loc_name    = NULL;
258    int         grOffset    = 0;
259
260    int32_t         buflen          = 512;
261    UErrorCode      status          = U_ZERO_ERROR;
262
263
264    if( strcmp(tag_name, LOC_CANONICALIZE_TAG) != 0 ){
265        /* Handle  grandfathered languages */
266        grOffset =  findOffset( LOC_GRANDFATHERED , loc_name );
267        if( grOffset >= 0 ){
268            if( strcmp(tag_name , LOC_LANG_TAG)==0 ){
269                tag_value = estrdup(loc_name);
270                return tag_value;
271            } else {
272                /* Since Grandfathered , no value , do nothing , retutn NULL */
273                return NULL;
274            }
275        }
276
277    if( fromParseLocale==1 ){
278        /* Handle singletons */
279        if( strcmp(tag_name , LOC_LANG_TAG)==0 ){
280            if( strlen(loc_name)>1 && (isIDPrefix(loc_name) ==1 ) ){
281                return loc_name;
282            }
283        }
284
285        singletonPos = getSingletonPos( loc_name );
286        if( singletonPos == 0){
287            /* singleton at start of script, region , variant etc.
288             * or invalid singleton at start of language */
289            return NULL;
290        } else if( singletonPos > 0 ){
291            /* singleton at some position except at start
292             * strip off the singleton and rest of the loc_name */
293            mod_loc_name = estrndup ( loc_name , singletonPos-1);
294        }
295    } /* end of if fromParse */
296
297    } /* end of if != LOC_CANONICAL_TAG */
298
299    if( mod_loc_name == NULL){
300        mod_loc_name = estrdup(loc_name );
301    }
302
303    /* Proceed to ICU */
304    do{
305        tag_value = erealloc( tag_value , buflen  );
306        tag_value_len = buflen;
307
308        if( strcmp(tag_name , LOC_SCRIPT_TAG)==0 ){
309            buflen = uloc_getScript ( mod_loc_name ,tag_value , tag_value_len , &status);
310        }
311        if( strcmp(tag_name , LOC_LANG_TAG )==0 ){
312            buflen = uloc_getLanguage ( mod_loc_name ,tag_value , tag_value_len , &status);
313        }
314        if( strcmp(tag_name , LOC_REGION_TAG)==0 ){
315            buflen = uloc_getCountry ( mod_loc_name ,tag_value , tag_value_len , &status);
316        }
317        if( strcmp(tag_name , LOC_VARIANT_TAG)==0 ){
318            buflen = uloc_getVariant ( mod_loc_name ,tag_value , tag_value_len , &status);
319        }
320        if( strcmp(tag_name , LOC_CANONICALIZE_TAG)==0 ){
321            buflen = uloc_canonicalize ( mod_loc_name ,tag_value , tag_value_len , &status);
322        }
323
324        if( U_FAILURE( status ) ) {
325            if( status == U_BUFFER_OVERFLOW_ERROR ) {
326                status = U_ZERO_ERROR;
327                continue;
328            }
329
330            /* Error in retriving data */
331            *result = 0;
332            if( tag_value ){
333                efree( tag_value );
334            }
335            if( mod_loc_name ){
336                efree( mod_loc_name);
337            }
338            return NULL;
339        }
340    } while( buflen > tag_value_len );
341
342    if(  buflen ==0 ){
343        /* No value found */
344        *result = -1;
345        if( tag_value ){
346            efree( tag_value );
347        }
348        if( mod_loc_name ){
349            efree( mod_loc_name);
350        }
351        return NULL;
352    } else {
353        *result = 1;
354    }
355
356    if( mod_loc_name ){
357        efree( mod_loc_name);
358    }
359    return tag_value;
360}
361/* }}} */
362
363/* {{{
364* Gets the value from ICU , called when PHP userspace function is called
365* common code shared by get_primary_language,get_script or get_region or get_variant
366*/
367static void get_icu_value_src_php( char* tag_name, INTERNAL_FUNCTION_PARAMETERS)
368{
369
370    char*       loc_name            = NULL;
371    int         loc_name_len        = 0;
372
373    char*       tag_value       = NULL;
374    char*       empty_result    = "";
375
376    int         result          = 0;
377    char*       msg             = NULL;
378
379    UErrorCode  status              = U_ZERO_ERROR;
380
381    intl_error_reset( NULL TSRMLS_CC );
382
383    if(zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "s",
384    &loc_name ,&loc_name_len ) == FAILURE) {
385        spprintf(&msg , 0, "locale_get_%s : unable to parse input params", tag_name );
386        intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR,  msg , 1 TSRMLS_CC );
387        efree(msg);
388
389        RETURN_FALSE;
390    }
391
392    if(loc_name_len == 0) {
393        loc_name = intl_locale_get_default(TSRMLS_C);
394    }
395
396    /* Call ICU get */
397    tag_value = get_icu_value_internal( loc_name , tag_name , &result ,0);
398
399    /* No value found */
400    if( result == -1 ) {
401        if( tag_value){
402            efree( tag_value);
403        }
404        RETURN_STRING( empty_result , TRUE);
405    }
406
407    /* value found */
408    if( tag_value){
409        RETURN_STRING( tag_value , FALSE);
410    }
411
412    /* Error encountered while fetching the value */
413    if( result ==0) {
414        spprintf(&msg , 0, "locale_get_%s : unable to get locale %s", tag_name , tag_name );
415        intl_error_set( NULL, status, msg , 1 TSRMLS_CC );
416        efree(msg);
417        RETURN_NULL();
418    }
419
420}
421/* }}} */
422
423/* {{{ proto static string Locale::getScript($locale)
424 * gets the script for the $locale
425 }}} */
426/* {{{ proto static string locale_get_script($locale)
427 * gets the script for the $locale
428 */
429PHP_FUNCTION( locale_get_script )
430{
431    get_icu_value_src_php( LOC_SCRIPT_TAG , INTERNAL_FUNCTION_PARAM_PASSTHRU );
432}
433/* }}} */
434
435/* {{{ proto static string Locale::getRegion($locale)
436 * gets the region for the $locale
437 }}} */
438/* {{{ proto static string locale_get_region($locale)
439 * gets the region for the $locale
440 */
441PHP_FUNCTION( locale_get_region )
442{
443    get_icu_value_src_php( LOC_REGION_TAG , INTERNAL_FUNCTION_PARAM_PASSTHRU );
444}
445/* }}} */
446
447/* {{{ proto static string Locale::getPrimaryLanguage($locale)
448 * gets the primary language for the $locale
449 }}} */
450/* {{{ proto static string locale_get_primary_language($locale)
451 * gets the primary language for the $locale
452 */
453PHP_FUNCTION(locale_get_primary_language )
454{
455    get_icu_value_src_php( LOC_LANG_TAG , INTERNAL_FUNCTION_PARAM_PASSTHRU );
456}
457/* }}} */
458
459
460/* {{{
461 * common code shared by display_xyz functions to  get the value from ICU
462 }}} */
463static void get_icu_disp_value_src_php( char* tag_name, INTERNAL_FUNCTION_PARAMETERS)
464{
465    char*       loc_name            = NULL;
466    int         loc_name_len        = 0;
467
468    char*       disp_loc_name       = NULL;
469    int         disp_loc_name_len   = 0;
470    int         free_loc_name       = 0;
471
472    UChar*      disp_name       = NULL;
473    int32_t     disp_name_len   = 0;
474
475    char*       mod_loc_name        = NULL;
476
477    int32_t     buflen              = 512;
478    UErrorCode  status              = U_ZERO_ERROR;
479
480    char*       utf8value       = NULL;
481    int         utf8value_len       = 0;
482
483    char*       msg                 = NULL;
484    int         grOffset        = 0;
485
486    intl_error_reset( NULL TSRMLS_CC );
487
488    if(zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "s|s",
489        &loc_name, &loc_name_len ,
490        &disp_loc_name ,&disp_loc_name_len ) == FAILURE)
491    {
492        spprintf(&msg , 0, "locale_get_display_%s : unable to parse input params", tag_name );
493        intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR,  msg , 1 TSRMLS_CC );
494        efree(msg);
495        RETURN_FALSE;
496    }
497
498    if(loc_name_len == 0) {
499        loc_name = intl_locale_get_default(TSRMLS_C);
500    }
501
502    if( strcmp(tag_name, DISP_NAME) != 0 ){
503        /* Handle grandfathered languages */
504        grOffset = findOffset( LOC_GRANDFATHERED , loc_name );
505        if( grOffset >= 0 ){
506            if( strcmp(tag_name , LOC_LANG_TAG)==0 ){
507                mod_loc_name = getPreferredTag( loc_name );
508            } else {
509                /* Since Grandfathered, no value, do nothing, retutn NULL */
510                RETURN_FALSE;
511            }
512        }
513    } /* end of if != LOC_CANONICAL_TAG */
514
515    if( mod_loc_name==NULL ){
516        mod_loc_name = estrdup( loc_name );
517    }
518
519    /* Check if disp_loc_name passed , if not use default locale */
520    if( !disp_loc_name){
521        disp_loc_name = estrdup(intl_locale_get_default(TSRMLS_C));
522        free_loc_name = 1;
523    }
524
525    /* Get the disp_value for the given locale */
526    do{
527        disp_name = erealloc( disp_name , buflen * sizeof(UChar)  );
528        disp_name_len = buflen;
529
530        if( strcmp(tag_name , LOC_LANG_TAG)==0 ){
531            buflen = uloc_getDisplayLanguage ( mod_loc_name , disp_loc_name , disp_name , disp_name_len , &status);
532        } else if( strcmp(tag_name , LOC_SCRIPT_TAG)==0 ){
533            buflen = uloc_getDisplayScript ( mod_loc_name , disp_loc_name , disp_name , disp_name_len , &status);
534        } else if( strcmp(tag_name , LOC_REGION_TAG)==0 ){
535            buflen = uloc_getDisplayCountry ( mod_loc_name , disp_loc_name , disp_name , disp_name_len , &status);
536        } else if( strcmp(tag_name , LOC_VARIANT_TAG)==0 ){
537            buflen = uloc_getDisplayVariant ( mod_loc_name , disp_loc_name , disp_name , disp_name_len , &status);
538        } else if( strcmp(tag_name , DISP_NAME)==0 ){
539            buflen = uloc_getDisplayName ( mod_loc_name , disp_loc_name , disp_name , disp_name_len , &status);
540        }
541
542        /* U_STRING_NOT_TERMINATED_WARNING is admissible here; don't look for it */
543        if( U_FAILURE( status ) )
544        {
545            if( status == U_BUFFER_OVERFLOW_ERROR )
546            {
547                status = U_ZERO_ERROR;
548                continue;
549            }
550
551            spprintf(&msg, 0, "locale_get_display_%s : unable to get locale %s", tag_name , tag_name );
552            intl_error_set( NULL, status, msg , 1 TSRMLS_CC );
553            efree(msg);
554            if( disp_name){
555                efree( disp_name );
556            }
557            if( mod_loc_name){
558                efree( mod_loc_name );
559            }
560            if (free_loc_name) {
561                efree(disp_loc_name);
562                disp_loc_name = NULL;
563            }
564            RETURN_FALSE;
565        }
566    } while( buflen > disp_name_len );
567
568    if( mod_loc_name){
569        efree( mod_loc_name );
570    }
571    if (free_loc_name) {
572        efree(disp_loc_name);
573        disp_loc_name = NULL;
574    }
575    /* Convert display locale name from UTF-16 to UTF-8. */
576    intl_convert_utf16_to_utf8( &utf8value, &utf8value_len, disp_name, buflen, &status );
577    efree( disp_name );
578    if( U_FAILURE( status ) )
579    {
580        spprintf(&msg, 0, "locale_get_display_%s :error converting display name for %s to UTF-8", tag_name , tag_name );
581        intl_error_set( NULL, status, msg , 1 TSRMLS_CC );
582        efree(msg);
583        RETURN_FALSE;
584    }
585
586    RETVAL_STRINGL( utf8value, utf8value_len , FALSE);
587
588}
589/* }}} */
590
591/* {{{ proto static string Locale::getDisplayName($locale[, $in_locale = null])
592* gets the name for the $locale in $in_locale or default_locale
593 }}} */
594/* {{{ proto static string get_display_name($locale[, $in_locale = null])
595* gets the name for the $locale in $in_locale or default_locale
596*/
597PHP_FUNCTION(locale_get_display_name)
598{
599    get_icu_disp_value_src_php( DISP_NAME , INTERNAL_FUNCTION_PARAM_PASSTHRU );
600}
601/* }}} */
602
603/* {{{ proto static string Locale::getDisplayLanguage($locale[, $in_locale = null])
604* gets the language for the $locale in $in_locale or default_locale
605 }}} */
606/* {{{ proto static string get_display_language($locale[, $in_locale = null])
607* gets the language for the $locale in $in_locale or default_locale
608*/
609PHP_FUNCTION(locale_get_display_language)
610{
611    get_icu_disp_value_src_php( LOC_LANG_TAG , INTERNAL_FUNCTION_PARAM_PASSTHRU );
612}
613/* }}} */
614
615/* {{{ proto static string Locale::getDisplayScript($locale, $in_locale = null)
616* gets the script for the $locale in $in_locale or default_locale
617 }}} */
618/* {{{ proto static string get_display_script($locale, $in_locale = null)
619* gets the script for the $locale in $in_locale or default_locale
620*/
621PHP_FUNCTION(locale_get_display_script)
622{
623    get_icu_disp_value_src_php( LOC_SCRIPT_TAG , INTERNAL_FUNCTION_PARAM_PASSTHRU );
624}
625/* }}} */
626
627/* {{{ proto static string Locale::getDisplayRegion($locale, $in_locale = null)
628* gets the region for the $locale in $in_locale or default_locale
629 }}} */
630/* {{{ proto static string get_display_region($locale, $in_locale = null)
631* gets the region for the $locale in $in_locale or default_locale
632*/
633PHP_FUNCTION(locale_get_display_region)
634{
635    get_icu_disp_value_src_php( LOC_REGION_TAG , INTERNAL_FUNCTION_PARAM_PASSTHRU );
636}
637/* }}} */
638
639/* {{{
640* proto static string Locale::getDisplayVariant($locale, $in_locale = null)
641* gets the variant for the $locale in $in_locale or default_locale
642 }}} */
643/* {{{
644* proto static string get_display_variant($locale, $in_locale = null)
645* gets the variant for the $locale in $in_locale or default_locale
646*/
647PHP_FUNCTION(locale_get_display_variant)
648{
649    get_icu_disp_value_src_php( LOC_VARIANT_TAG , INTERNAL_FUNCTION_PARAM_PASSTHRU );
650}
651/* }}} */
652
653 /* {{{ proto static array getKeywords(string $locale) {
654 * return an associative array containing keyword-value
655 * pairs for this locale. The keys are keys to the array (doh!)
656 * }}}*/
657 /* {{{ proto static array locale_get_keywords(string $locale) {
658 * return an associative array containing keyword-value
659 * pairs for this locale. The keys are keys to the array (doh!)
660 */
661PHP_FUNCTION( locale_get_keywords )
662{
663    UEnumeration*   e        = NULL;
664    UErrorCode      status   = U_ZERO_ERROR;
665
666    const char*     kw_key        = NULL;
667    int32_t         kw_key_len    = 0;
668
669    char*           loc_name        = NULL;
670    int             loc_name_len    = 0;
671
672/*
673    ICU expects the buffer to be allocated  before calling the function
674    and so the buffer size has been explicitly specified
675    ICU uloc.h #define  ULOC_KEYWORD_AND_VALUES_CAPACITY   100
676    hence the kw_value buffer size is 100
677*/
678    char*       kw_value        = NULL;
679    int32_t     kw_value_len    = 100;
680
681    intl_error_reset( NULL TSRMLS_CC );
682
683    if(zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "s",
684        &loc_name, &loc_name_len ) == FAILURE)
685    {
686        intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR,
687             "locale_get_keywords: unable to parse input params", 0 TSRMLS_CC );
688
689        RETURN_FALSE;
690    }
691
692    if(loc_name_len == 0) {
693        loc_name = intl_locale_get_default(TSRMLS_C);
694    }
695
696    /* Get the keywords */
697    e = uloc_openKeywords( loc_name, &status );
698    if( e != NULL )
699    {
700        /* Traverse it, filling the return array. */
701        array_init( return_value );
702
703        while( ( kw_key = uenum_next( e, &kw_key_len, &status ) ) != NULL ){
704            kw_value = ecalloc( 1 , kw_value_len  );
705
706            /* Get the keyword value for each keyword */
707            kw_value_len=uloc_getKeywordValue( loc_name,kw_key, kw_value, kw_value_len ,  &status );
708            if (status == U_BUFFER_OVERFLOW_ERROR) {
709                status = U_ZERO_ERROR;
710                kw_value = erealloc( kw_value , kw_value_len+1);
711                kw_value_len=uloc_getKeywordValue( loc_name,kw_key, kw_value, kw_value_len+1 ,  &status );
712            } else if(!U_FAILURE(status)) {
713                kw_value = erealloc( kw_value , kw_value_len+1);
714            }
715            if (U_FAILURE(status)) {
716                intl_error_set( NULL, FAILURE, "locale_get_keywords: Error encountered while getting the keyword  value for the  keyword", 0 TSRMLS_CC );
717                if( kw_value){
718                    efree( kw_value );
719                }
720                zval_dtor(return_value);
721                RETURN_FALSE;
722            }
723
724            add_assoc_stringl( return_value, (char *)kw_key, kw_value , kw_value_len, 0);
725        } /* end of while */
726
727    } /* end of if e!=NULL */
728
729    uenum_close( e );
730}
731/* }}} */
732
733 /* {{{ proto static string Locale::canonicalize($locale)
734 * @return string the canonicalized locale
735 * }}} */
736 /* {{{ proto static string locale_canonicalize(Locale $loc, string $locale)
737 * @param string $locale    The locale string to canonicalize
738 */
739PHP_FUNCTION(locale_canonicalize)
740{
741    get_icu_value_src_php( LOC_CANONICALIZE_TAG , INTERNAL_FUNCTION_PARAM_PASSTHRU );
742}
743/* }}} */
744
745/* {{{ append_key_value
746* Internal function which is called from locale_compose
747* gets the value for the key_name and appends to the loc_name
748* returns 1 if successful , -1 if not found ,
749* 0 if array element is not a string , -2 if buffer-overflow
750*/
751static int append_key_value(smart_str* loc_name, HashTable* hash_arr, char* key_name)
752{
753    zval**  ele_value   = NULL;
754
755    if(zend_hash_find(hash_arr , key_name , strlen(key_name) + 1 ,(void **)&ele_value ) == SUCCESS ) {
756        if(Z_TYPE_PP(ele_value)!= IS_STRING ){
757            /* element value is not a string */
758            return FAILURE;
759        }
760        if(strcmp(key_name, LOC_LANG_TAG) != 0 &&
761           strcmp(key_name, LOC_GRANDFATHERED_LANG_TAG)!=0 ) {
762            /* not lang or grandfathered tag */
763            smart_str_appendl(loc_name, SEPARATOR , sizeof(SEPARATOR)-1);
764        }
765        smart_str_appendl(loc_name, Z_STRVAL_PP(ele_value) , Z_STRLEN_PP(ele_value));
766        return SUCCESS;
767    }
768
769    return LOC_NOT_FOUND;
770}
771/* }}} */
772
773/* {{{ append_prefix , appends the prefix needed
774* e.g. private adds 'x'
775*/
776static void add_prefix(smart_str* loc_name, char* key_name)
777{
778    if( strncmp(key_name , LOC_PRIVATE_TAG , 7) == 0 ){
779        smart_str_appendl(loc_name, SEPARATOR , sizeof(SEPARATOR)-1);
780        smart_str_appendl(loc_name, PRIVATE_PREFIX , sizeof(PRIVATE_PREFIX)-1);
781    }
782}
783/* }}} */
784
785/* {{{ append_multiple_key_values
786* Internal function which is called from locale_compose
787* gets the multiple values for the key_name and appends to the loc_name
788* used for 'variant','extlang','private'
789* returns 1 if successful , -1 if not found ,
790* 0 if array element is not a string , -2 if buffer-overflow
791*/
792static int append_multiple_key_values(smart_str* loc_name, HashTable* hash_arr, char* key_name TSRMLS_DC)
793{
794    zval**  ele_value       = NULL;
795    int     i       = 0;
796    int     isFirstSubtag   = 0;
797    int     max_value   = 0;
798
799    /* Variant/ Extlang/Private etc. */
800    if( zend_hash_find( hash_arr , key_name , strlen(key_name) + 1 ,(void **)&ele_value ) == SUCCESS ) {
801        if( Z_TYPE_PP(ele_value) == IS_STRING ){
802            add_prefix( loc_name , key_name);
803
804            smart_str_appendl(loc_name, SEPARATOR , sizeof(SEPARATOR)-1);
805            smart_str_appendl(loc_name, Z_STRVAL_PP(ele_value) , Z_STRLEN_PP(ele_value));
806            return SUCCESS;
807        } else if(Z_TYPE_PP(ele_value) == IS_ARRAY ) {
808            HashPosition pos;
809            HashTable *arr = HASH_OF(*ele_value);
810            zval **data = NULL;
811
812            zend_hash_internal_pointer_reset_ex(arr, &pos);
813            while(zend_hash_get_current_data_ex(arr, (void **)&data, &pos) != FAILURE) {
814                if(Z_TYPE_PP(data) != IS_STRING) {
815                    return FAILURE;
816                }
817                if (isFirstSubtag++ == 0){
818                    add_prefix(loc_name , key_name);
819                }
820                smart_str_appendl(loc_name, SEPARATOR , sizeof(SEPARATOR)-1);
821                smart_str_appendl(loc_name, Z_STRVAL_PP(data) , Z_STRLEN_PP(data));
822                zend_hash_move_forward_ex(arr, &pos);
823            }
824            return SUCCESS;
825        } else {
826            return FAILURE;
827        }
828    } else {
829        char cur_key_name[31];
830        /* Decide the max_value: the max. no. of elements allowed */
831        if( strcmp(key_name , LOC_VARIANT_TAG) ==0 ){
832            max_value  = MAX_NO_VARIANT;
833        }
834        if( strcmp(key_name , LOC_EXTLANG_TAG) ==0 ){
835            max_value  = MAX_NO_EXTLANG;
836        }
837        if( strcmp(key_name , LOC_PRIVATE_TAG) ==0 ){
838            max_value  = MAX_NO_PRIVATE;
839        }
840
841        /* Multiple variant values as variant0, variant1 ,variant2 */
842        isFirstSubtag = 0;
843        for( i=0 ; i< max_value; i++ ){
844            snprintf( cur_key_name , 30, "%s%d", key_name , i);
845            if( zend_hash_find( hash_arr , cur_key_name , strlen(cur_key_name) + 1,(void **)&ele_value ) == SUCCESS ){
846                if( Z_TYPE_PP(ele_value)!= IS_STRING ){
847                    /* variant is not a string */
848                    return FAILURE;
849                }
850                /* Add the contents */
851                if (isFirstSubtag++ == 0){
852                    add_prefix(loc_name , cur_key_name);
853                }
854                smart_str_appendl(loc_name, SEPARATOR , sizeof(SEPARATOR)-1);
855                smart_str_appendl(loc_name, Z_STRVAL_PP(ele_value) , Z_STRLEN_PP(ele_value));
856            }
857        } /* end of for */
858    } /* end of else */
859
860    return SUCCESS;
861}
862/* }}} */
863
864/*{{{
865* If applicable sets error message and aborts locale_compose gracefully
866* returns 0  if locale_compose needs to be aborted
867* otherwise returns 1
868*/
869static int handleAppendResult( int result, smart_str* loc_name TSRMLS_DC)
870{
871    intl_error_reset( NULL TSRMLS_CC );
872    if( result == FAILURE) {
873        intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR,
874             "locale_compose: parameter array element is not a string", 0 TSRMLS_CC );
875        smart_str_free(loc_name);
876        return 0;
877    }
878    return 1;
879}
880/* }}} */
881
882#define RETURN_SMART_STR(s) smart_str_0((s)); RETURN_STRINGL((s)->c, (s)->len, 0)
883/* {{{ proto static string Locale::composeLocale($array)
884* Creates a locale by combining the parts of locale-ID passed
885* }}} */
886/* {{{ proto static string compose_locale($array)
887* Creates a locale by combining the parts of locale-ID passed
888* }}} */
889PHP_FUNCTION(locale_compose)
890{
891    smart_str       loc_name_s = {0};
892    smart_str *loc_name = &loc_name_s;
893    zval*           arr = NULL;
894    HashTable*      hash_arr = NULL;
895    int             result = 0;
896
897    intl_error_reset( NULL TSRMLS_CC );
898
899    if(zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "a",
900        &arr) == FAILURE)
901    {
902        intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR,
903             "locale_compose: unable to parse input params", 0 TSRMLS_CC );
904        RETURN_FALSE;
905    }
906
907    hash_arr = HASH_OF( arr );
908
909    if( !hash_arr || zend_hash_num_elements( hash_arr ) == 0 )
910        RETURN_FALSE;
911
912    /* Check for grandfathered first */
913    result = append_key_value(loc_name, hash_arr,  LOC_GRANDFATHERED_LANG_TAG);
914    if( result == SUCCESS){
915        RETURN_SMART_STR(loc_name);
916    }
917    if( !handleAppendResult( result, loc_name TSRMLS_CC)){
918        RETURN_FALSE;
919    }
920
921    /* Not grandfathered */
922    result = append_key_value(loc_name, hash_arr , LOC_LANG_TAG);
923    if( result == LOC_NOT_FOUND ){
924        intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR,
925        "locale_compose: parameter array does not contain 'language' tag.", 0 TSRMLS_CC );
926        smart_str_free(loc_name);
927        RETURN_FALSE;
928    }
929    if( !handleAppendResult( result, loc_name TSRMLS_CC)){
930        RETURN_FALSE;
931    }
932
933    /* Extlang */
934    result = append_multiple_key_values(loc_name, hash_arr , LOC_EXTLANG_TAG TSRMLS_CC);
935    if( !handleAppendResult( result, loc_name TSRMLS_CC)){
936        RETURN_FALSE;
937    }
938
939    /* Script */
940    result = append_key_value(loc_name, hash_arr , LOC_SCRIPT_TAG);
941    if( !handleAppendResult( result, loc_name TSRMLS_CC)){
942        RETURN_FALSE;
943    }
944
945    /* Region */
946    result = append_key_value( loc_name, hash_arr , LOC_REGION_TAG);
947    if( !handleAppendResult( result, loc_name TSRMLS_CC)){
948        RETURN_FALSE;
949    }
950
951    /* Variant */
952    result = append_multiple_key_values( loc_name, hash_arr , LOC_VARIANT_TAG TSRMLS_CC);
953    if( !handleAppendResult( result, loc_name TSRMLS_CC)){
954        RETURN_FALSE;
955    }
956
957    /* Private */
958    result = append_multiple_key_values( loc_name, hash_arr , LOC_PRIVATE_TAG TSRMLS_CC);
959    if( !handleAppendResult( result, loc_name TSRMLS_CC)){
960        RETURN_FALSE;
961    }
962
963    RETURN_SMART_STR(loc_name);
964}
965/* }}} */
966
967
968/*{{{
969* Parses the locale and returns private subtags  if existing
970* else returns NULL
971* e.g. for locale='en_US-x-prv1-prv2-prv3'
972* returns a pointer to the string 'prv1-prv2-prv3'
973*/
974static char* get_private_subtags(char* loc_name)
975{
976    char*   result =NULL;
977    int     singletonPos = 0;
978    int     len =0;
979    char*   mod_loc_name =NULL;
980
981    if( loc_name && (len = strlen(loc_name)>0 ) ){
982        mod_loc_name = loc_name ;
983        len   = strlen(mod_loc_name);
984        while( (singletonPos = getSingletonPos(mod_loc_name))!= -1){
985
986            if( singletonPos!=-1){
987                if( (*(mod_loc_name+singletonPos)=='x') || (*(mod_loc_name+singletonPos)=='X') ){
988                    /* private subtag start found */
989                    if( singletonPos + 2 ==  len){
990                        /* loc_name ends with '-x-' ; return  NULL */
991                    }
992                    else{
993                        /* result = mod_loc_name + singletonPos +2; */
994                        result = estrndup(mod_loc_name + singletonPos+2  , (len -( singletonPos +2) ) );
995                    }
996                    break;
997                }
998                else{
999                    if( singletonPos + 1 >=  len){
1000                        /* String end */
1001                        break;
1002                    } else {
1003                        /* singleton found but not a private subtag , hence check further in the string for the private subtag */
1004                        mod_loc_name = mod_loc_name + singletonPos +1;
1005                        len = strlen(mod_loc_name);
1006                    }
1007                }
1008            }
1009
1010        } /* end of while */
1011    }
1012
1013    return result;
1014}
1015/* }}} */
1016
1017/* {{{ code used by locale_parse
1018*/
1019static int add_array_entry(char* loc_name, zval* hash_arr, char* key_name TSRMLS_DC)
1020{
1021    char*   key_value   = NULL;
1022    char*   cur_key_name    = NULL;
1023    char*   token           = NULL;
1024    char*   last_ptr    = NULL;
1025
1026    int result      = 0;
1027    int     cur_result      = 0;
1028    int     cnt         = 0;
1029
1030
1031    if( strcmp(key_name , LOC_PRIVATE_TAG)==0 ){
1032        key_value = get_private_subtags( loc_name );
1033        result = 1;
1034    } else {
1035        key_value = get_icu_value_internal( loc_name , key_name , &result,1 );
1036    }
1037    if( (strcmp(key_name , LOC_PRIVATE_TAG)==0) ||
1038        ( strcmp(key_name , LOC_VARIANT_TAG)==0) ){
1039        if( result > 0 && key_value){
1040            /* Tokenize on the "_" or "-"  */
1041            token = php_strtok_r( key_value , DELIMITER ,&last_ptr);
1042            if( cur_key_name ){
1043                efree( cur_key_name);
1044            }
1045            cur_key_name = (char*)ecalloc( 25,  25);
1046            sprintf( cur_key_name , "%s%d", key_name , cnt++);
1047            add_assoc_string( hash_arr, cur_key_name , token ,TRUE );
1048            /* tokenize on the "_" or "-" and stop  at singleton if any */
1049            while( (token = php_strtok_r(NULL , DELIMITER , &last_ptr)) && (strlen(token)>1) ){
1050                sprintf( cur_key_name , "%s%d", key_name , cnt++);
1051                add_assoc_string( hash_arr, cur_key_name , token , TRUE );
1052            }
1053/*
1054            if( strcmp(key_name, LOC_PRIVATE_TAG) == 0 ){
1055            }
1056*/
1057        }
1058    } else {
1059        if( result == 1 ){
1060            add_assoc_string( hash_arr, key_name , key_value , TRUE );
1061            cur_result = 1;
1062        }
1063    }
1064
1065    if( cur_key_name ){
1066        efree( cur_key_name);
1067    }
1068    /*if( key_name != LOC_PRIVATE_TAG && key_value){*/
1069    if( key_value){
1070        efree(key_value);
1071    }
1072    return cur_result;
1073}
1074/* }}} */
1075
1076/* {{{ proto static array Locale::parseLocale($locale)
1077* parses a locale-id into an array the different parts of it
1078 }}} */
1079/* {{{ proto static array parse_locale($locale)
1080* parses a locale-id into an array the different parts of it
1081*/
1082PHP_FUNCTION(locale_parse)
1083{
1084    char*       loc_name        = NULL;
1085    int         loc_name_len    = 0;
1086    int         grOffset        = 0;
1087
1088    intl_error_reset( NULL TSRMLS_CC );
1089
1090    if(zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "s",
1091        &loc_name, &loc_name_len ) == FAILURE)
1092    {
1093        intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR,
1094             "locale_parse: unable to parse input params", 0 TSRMLS_CC );
1095
1096        RETURN_FALSE;
1097    }
1098
1099    if(loc_name_len == 0) {
1100        loc_name = intl_locale_get_default(TSRMLS_C);
1101    }
1102
1103    array_init( return_value );
1104
1105    grOffset =  findOffset( LOC_GRANDFATHERED , loc_name );
1106    if( grOffset >= 0 ){
1107        add_assoc_string( return_value , LOC_GRANDFATHERED_LANG_TAG , estrdup(loc_name) ,FALSE );
1108    }
1109    else{
1110        /* Not grandfathered */
1111        add_array_entry( loc_name , return_value , LOC_LANG_TAG TSRMLS_CC);
1112        add_array_entry( loc_name , return_value , LOC_SCRIPT_TAG TSRMLS_CC);
1113        add_array_entry( loc_name , return_value , LOC_REGION_TAG TSRMLS_CC);
1114        add_array_entry( loc_name , return_value , LOC_VARIANT_TAG TSRMLS_CC);
1115        add_array_entry( loc_name , return_value , LOC_PRIVATE_TAG TSRMLS_CC);
1116    }
1117}
1118/* }}} */
1119
1120/* {{{ proto static array Locale::getAllVariants($locale)
1121* gets an array containing the list of variants, or null
1122 }}} */
1123/* {{{ proto static array locale_get_all_variants($locale)
1124* gets an array containing the list of variants, or null
1125*/
1126PHP_FUNCTION(locale_get_all_variants)
1127{
1128    char*   loc_name        = NULL;
1129    int     loc_name_len    = 0;
1130
1131    int result      = 0;
1132    char*   token       = NULL;
1133    char*   variant     = NULL;
1134    char*   saved_ptr   = NULL;
1135
1136    intl_error_reset( NULL TSRMLS_CC );
1137
1138    if(zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "s",
1139    &loc_name, &loc_name_len ) == FAILURE)
1140    {
1141        intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR,
1142         "locale_parse: unable to parse input params", 0 TSRMLS_CC );
1143
1144        RETURN_FALSE;
1145    }
1146
1147    if(loc_name_len == 0) {
1148        loc_name = intl_locale_get_default(TSRMLS_C);
1149    }
1150
1151
1152    array_init( return_value );
1153
1154    /* If the locale is grandfathered, stop, no variants */
1155    if( findOffset( LOC_GRANDFATHERED , loc_name ) >=  0 ){
1156        /* ("Grandfathered Tag. No variants."); */
1157    }
1158    else {
1159    /* Call ICU variant */
1160        variant = get_icu_value_internal( loc_name , LOC_VARIANT_TAG , &result ,0);
1161        if( result > 0 && variant){
1162            /* Tokenize on the "_" or "-" */
1163            token = php_strtok_r( variant , DELIMITER , &saved_ptr);
1164            add_next_index_stringl( return_value, token , strlen(token) ,TRUE );
1165            /* tokenize on the "_" or "-" and stop  at singleton if any */
1166            while( (token = php_strtok_r(NULL , DELIMITER, &saved_ptr)) && (strlen(token)>1) ){
1167                add_next_index_stringl( return_value, token , strlen(token) ,TRUE );
1168            }
1169        }
1170        if( variant ){
1171            efree( variant );
1172        }
1173    }
1174
1175
1176}
1177/* }}} */
1178
1179/*{{{
1180* Converts to lower case and also replaces all hyphens with the underscore
1181*/
1182static int strToMatch(char* str ,char *retstr)
1183{
1184    char*   anchor  = NULL;
1185    char*   anchor1 = NULL;
1186    int     result  = 0;
1187    int     len     = 0;
1188
1189    if( (!str) || str[0] == '\0'){
1190        return result;
1191    } else {
1192    anchor = retstr;
1193    anchor1 = str;
1194        len = strlen(str);
1195        while( (*str)!='\0' ){
1196        if( *str == '-' ){
1197            *retstr =  '_';
1198        } else {
1199            *retstr = tolower(*str);
1200        }
1201            str++;
1202            retstr++;
1203    }
1204    *retstr = '\0';
1205    retstr=  anchor;
1206    str=  anchor1;
1207    result = 1;
1208    }
1209
1210    return(result);
1211}
1212/* }}} */
1213
1214/* {{{ proto static boolean Locale::filterMatches(string $langtag, string $locale[, bool $canonicalize])
1215* Checks if a $langtag filter matches with $locale according to RFC 4647's basic filtering algorithm
1216*/
1217/* }}} */
1218/* {{{ proto boolean locale_filter_matches(string $langtag, string $locale[, bool $canonicalize])
1219* Checks if a $langtag filter matches with $locale according to RFC 4647's basic filtering algorithm
1220*/
1221PHP_FUNCTION(locale_filter_matches)
1222{
1223    char*           lang_tag        = NULL;
1224    int             lang_tag_len    = 0;
1225    char*           loc_range       = NULL;
1226    int             loc_range_len   = 0;
1227
1228    int     result      = 0;
1229    char*       token       = 0;
1230    char*       chrcheck    = NULL;
1231
1232    char*           can_lang_tag    = NULL;
1233    char*           can_loc_range   = NULL;
1234
1235    char*           cur_lang_tag    = NULL;
1236    char*           cur_loc_range   = NULL;
1237
1238    zend_bool   boolCanonical   = 0;
1239    UErrorCode  status      = U_ZERO_ERROR;
1240
1241    intl_error_reset( NULL TSRMLS_CC );
1242
1243    if(zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "ss|b",
1244        &lang_tag, &lang_tag_len , &loc_range , &loc_range_len ,
1245        &boolCanonical) == FAILURE)
1246    {
1247        intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR,
1248        "locale_filter_matches: unable to parse input params", 0 TSRMLS_CC );
1249
1250        RETURN_FALSE;
1251    }
1252
1253    if(loc_range_len == 0) {
1254        loc_range = intl_locale_get_default(TSRMLS_C);
1255    }
1256
1257    if( strcmp(loc_range,"*")==0){
1258        RETURN_TRUE;
1259    }
1260
1261    if( boolCanonical ){
1262        /* canonicalize loc_range */
1263        can_loc_range=get_icu_value_internal( loc_range , LOC_CANONICALIZE_TAG , &result , 0);
1264        if( result ==0) {
1265            intl_error_set( NULL, status,
1266                "locale_filter_matches : unable to canonicalize loc_range" , 0 TSRMLS_CC );
1267            RETURN_FALSE;
1268        }
1269
1270        /* canonicalize lang_tag */
1271        can_lang_tag = get_icu_value_internal( lang_tag , LOC_CANONICALIZE_TAG , &result ,  0);
1272        if( result ==0) {
1273            intl_error_set( NULL, status,
1274                "locale_filter_matches : unable to canonicalize lang_tag" , 0 TSRMLS_CC );
1275            RETURN_FALSE;
1276        }
1277
1278        /* Convert to lower case for case-insensitive comparison */
1279        cur_lang_tag = ecalloc( 1, strlen(can_lang_tag) + 1);
1280
1281        /* Convert to lower case for case-insensitive comparison */
1282        result = strToMatch( can_lang_tag , cur_lang_tag);
1283        if( result == 0) {
1284            efree( cur_lang_tag );
1285            efree( can_lang_tag );
1286            RETURN_FALSE;
1287        }
1288
1289        cur_loc_range = ecalloc( 1, strlen(can_loc_range) + 1);
1290        result = strToMatch( can_loc_range , cur_loc_range );
1291        if( result == 0) {
1292            efree( cur_lang_tag );
1293            efree( can_lang_tag );
1294            efree( cur_loc_range );
1295            efree( can_loc_range );
1296            RETURN_FALSE;
1297        }
1298
1299        /* check if prefix */
1300        token   = strstr( cur_lang_tag , cur_loc_range );
1301
1302        if( token && (token==cur_lang_tag) ){
1303            /* check if the char. after match is SEPARATOR */
1304            chrcheck = token + (strlen(cur_loc_range));
1305            if( isIDSeparator(*chrcheck) || isEndOfTag(*chrcheck) ){
1306                if( cur_lang_tag){
1307                    efree( cur_lang_tag );
1308                }
1309                if( cur_loc_range){
1310                    efree( cur_loc_range );
1311                }
1312                if( can_lang_tag){
1313                    efree( can_lang_tag );
1314                }
1315                if( can_loc_range){
1316                    efree( can_loc_range );
1317                }
1318                RETURN_TRUE;
1319            }
1320        }
1321
1322        /* No prefix as loc_range */
1323        if( cur_lang_tag){
1324            efree( cur_lang_tag );
1325        }
1326        if( cur_loc_range){
1327            efree( cur_loc_range );
1328        }
1329        if( can_lang_tag){
1330            efree( can_lang_tag );
1331        }
1332        if( can_loc_range){
1333            efree( can_loc_range );
1334        }
1335        RETURN_FALSE;
1336
1337    } /* end of if isCanonical */
1338    else{
1339        /* Convert to lower case for case-insensitive comparison */
1340        cur_lang_tag = ecalloc( 1, strlen(lang_tag ) + 1);
1341
1342        result = strToMatch( lang_tag , cur_lang_tag);
1343        if( result == 0) {
1344            efree( cur_lang_tag );
1345            RETURN_FALSE;
1346        }
1347        cur_loc_range = ecalloc( 1, strlen(loc_range ) + 1);
1348        result = strToMatch( loc_range , cur_loc_range );
1349        if( result == 0) {
1350            efree( cur_lang_tag );
1351            efree( cur_loc_range );
1352            RETURN_FALSE;
1353        }
1354
1355        /* check if prefix */
1356        token   = strstr( cur_lang_tag , cur_loc_range );
1357
1358        if( token && (token==cur_lang_tag) ){
1359            /* check if the char. after match is SEPARATOR */
1360            chrcheck = token + (strlen(cur_loc_range));
1361            if( isIDSeparator(*chrcheck) || isEndOfTag(*chrcheck) ){
1362                if( cur_lang_tag){
1363                    efree( cur_lang_tag );
1364                }
1365                if( cur_loc_range){
1366                    efree( cur_loc_range );
1367                }
1368                RETURN_TRUE;
1369            }
1370        }
1371
1372        /* No prefix as loc_range */
1373        if( cur_lang_tag){
1374            efree( cur_lang_tag );
1375        }
1376        if( cur_loc_range){
1377            efree( cur_loc_range );
1378        }
1379        RETURN_FALSE;
1380
1381    }
1382}
1383/* }}} */
1384
1385static void array_cleanup( char* arr[] , int arr_size)
1386{
1387    int i=0;
1388    for( i=0; i< arr_size; i++ ){
1389        if( arr[i*2] ){
1390            efree( arr[i*2]);
1391        }
1392    }
1393    efree(arr);
1394}
1395
1396#define LOOKUP_CLEAN_RETURN(value)  array_cleanup(cur_arr, cur_arr_len); return (value)
1397/* {{{
1398* returns the lookup result to lookup_loc_range_src_php
1399* internal function
1400*/
1401static char* lookup_loc_range(char* loc_range, HashTable* hash_arr, int canonicalize  TSRMLS_DC)
1402{
1403    int i = 0;
1404    int cur_arr_len = 0;
1405    int result = 0;
1406
1407    char* lang_tag = NULL;
1408    zval** ele_value = NULL;
1409    char** cur_arr = NULL;
1410
1411    char* cur_loc_range = NULL;
1412    char* can_loc_range = NULL;
1413    int saved_pos = 0;
1414
1415    char* return_value = NULL;
1416
1417    cur_arr = ecalloc(zend_hash_num_elements(hash_arr)*2, sizeof(char *));
1418    /* convert the array to lowercase , also replace hyphens with the underscore and store it in cur_arr */
1419    for(zend_hash_internal_pointer_reset(hash_arr);
1420        zend_hash_has_more_elements(hash_arr) == SUCCESS;
1421        zend_hash_move_forward(hash_arr)) {
1422
1423        if (zend_hash_get_current_data(hash_arr, (void**)&ele_value) == FAILURE) {
1424            /* Should never actually fail since the key is known to exist.*/
1425            continue;
1426        }
1427        if(Z_TYPE_PP(ele_value)!= IS_STRING) {
1428            /* element value is not a string */
1429            intl_error_set(NULL, U_ILLEGAL_ARGUMENT_ERROR, "lookup_loc_range: locale array element is not a string", 0 TSRMLS_CC);
1430            LOOKUP_CLEAN_RETURN(NULL);
1431        }
1432        cur_arr[cur_arr_len*2] = estrndup(Z_STRVAL_PP(ele_value), Z_STRLEN_PP(ele_value));
1433        result = strToMatch(Z_STRVAL_PP(ele_value), cur_arr[cur_arr_len*2]);
1434        if(result == 0) {
1435            intl_error_set(NULL, U_ILLEGAL_ARGUMENT_ERROR, "lookup_loc_range: unable to canonicalize lang_tag", 0 TSRMLS_CC);
1436            LOOKUP_CLEAN_RETURN(NULL);
1437        }
1438        cur_arr[cur_arr_len*2+1] = Z_STRVAL_PP(ele_value);
1439        cur_arr_len++ ;
1440    } /* end of for */
1441
1442    /* Canonicalize array elements */
1443    if(canonicalize) {
1444        for(i=0; i<cur_arr_len; i++) {
1445            lang_tag = get_icu_value_internal(cur_arr[i*2], LOC_CANONICALIZE_TAG, &result, 0);
1446            if(result != 1 || lang_tag == NULL || !lang_tag[0]) {
1447                if(lang_tag) {
1448                    efree(lang_tag);
1449                }
1450                intl_error_set(NULL, U_ILLEGAL_ARGUMENT_ERROR, "lookup_loc_range: unable to canonicalize lang_tag" , 0 TSRMLS_CC);
1451                LOOKUP_CLEAN_RETURN(NULL);
1452            }
1453            cur_arr[i*2] = erealloc(cur_arr[i*2], strlen(lang_tag)+1);
1454            result = strToMatch(lang_tag, cur_arr[i*2]);
1455            efree(lang_tag);
1456            if(result == 0) {
1457                intl_error_set(NULL, U_ILLEGAL_ARGUMENT_ERROR, "lookup_loc_range: unable to canonicalize lang_tag" , 0 TSRMLS_CC);
1458                LOOKUP_CLEAN_RETURN(NULL);
1459            }
1460        }
1461
1462    }
1463
1464    if(canonicalize) {
1465        /* Canonicalize the loc_range */
1466        can_loc_range = get_icu_value_internal(loc_range, LOC_CANONICALIZE_TAG, &result , 0);
1467        if( result != 1 || can_loc_range == NULL || !can_loc_range[0]) {
1468            /* Error */
1469            intl_error_set(NULL, U_ILLEGAL_ARGUMENT_ERROR, "lookup_loc_range: unable to canonicalize loc_range" , 0 TSRMLS_CC );
1470            if(can_loc_range) {
1471                efree(can_loc_range);
1472            }
1473            LOOKUP_CLEAN_RETURN(NULL);
1474        } else {
1475            loc_range = can_loc_range;
1476        }
1477    }
1478
1479    cur_loc_range = ecalloc(1, strlen(loc_range)+1);
1480    /* convert to lower and replace hyphens */
1481    result = strToMatch(loc_range, cur_loc_range);
1482    if(can_loc_range) {
1483        efree(can_loc_range);
1484    }
1485    if(result == 0) {
1486        intl_error_set(NULL, U_ILLEGAL_ARGUMENT_ERROR, "lookup_loc_range: unable to canonicalize lang_tag" , 0 TSRMLS_CC);
1487        LOOKUP_CLEAN_RETURN(NULL);
1488    }
1489
1490    /* Lookup for the lang_tag match */
1491    saved_pos = strlen(cur_loc_range);
1492    while(saved_pos > 0) {
1493        for(i=0; i< cur_arr_len; i++){
1494            if(cur_arr[i*2] != NULL && strlen(cur_arr[i*2]) == saved_pos && strncmp(cur_loc_range, cur_arr[i*2], saved_pos) == 0) {
1495                /* Match found */
1496                return_value = estrdup(canonicalize?cur_arr[i*2]:cur_arr[i*2+1]);
1497                efree(cur_loc_range);
1498                LOOKUP_CLEAN_RETURN(return_value);
1499            }
1500        }
1501        saved_pos = getStrrtokenPos(cur_loc_range, saved_pos);
1502    }
1503
1504    /* Match not found */
1505    efree(cur_loc_range);
1506    LOOKUP_CLEAN_RETURN(NULL);
1507}
1508/* }}} */
1509
1510/* {{{ proto string Locale::lookup(array $langtag, string $locale[, bool $canonicalize[, string $default = null]])
1511* Searchs the items in $langtag for the best match to the language
1512* range
1513*/
1514/* }}} */
1515/* {{{ proto string locale_lookup(array $langtag, string $locale[, bool $canonicalize[, string $default = null]])
1516* Searchs the items in $langtag for the best match to the language
1517* range
1518*/
1519PHP_FUNCTION(locale_lookup)
1520{
1521    char*       fallback_loc        = NULL;
1522    int         fallback_loc_len    = 0;
1523    char*       loc_range           = NULL;
1524    int         loc_range_len       = 0;
1525
1526    zval*       arr             = NULL;
1527    HashTable*  hash_arr        = NULL;
1528    zend_bool   boolCanonical   = 0;
1529    char*       result          =NULL;
1530
1531    intl_error_reset( NULL TSRMLS_CC );
1532
1533    if(zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "as|bs", &arr, &loc_range, &loc_range_len,
1534        &boolCanonical, &fallback_loc, &fallback_loc_len) == FAILURE) {
1535        intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR, "locale_lookup: unable to parse input params", 0 TSRMLS_CC );
1536        RETURN_FALSE;
1537    }
1538
1539    if(loc_range_len == 0) {
1540        loc_range = intl_locale_get_default(TSRMLS_C);
1541    }
1542
1543    hash_arr = HASH_OF(arr);
1544
1545    if( !hash_arr || zend_hash_num_elements( hash_arr ) == 0 ) {
1546        RETURN_EMPTY_STRING();
1547    }
1548
1549    result = lookup_loc_range(loc_range, hash_arr, boolCanonical TSRMLS_CC);
1550    if(result == NULL || result[0] == '\0') {
1551        if( fallback_loc ) {
1552            result = estrndup(fallback_loc, fallback_loc_len);
1553        } else {
1554            RETURN_EMPTY_STRING();
1555        }
1556    }
1557
1558    RETVAL_STRINGL(result, strlen(result), 0);
1559}
1560/* }}} */
1561
1562/* {{{ proto string Locale::acceptFromHttp(string $http_accept)
1563* Tries to find out best available locale based on HTTP �Accept-Language� header
1564*/
1565/* }}} */
1566/* {{{ proto string locale_accept_from_http(string $http_accept)
1567* Tries to find out best available locale based on HTTP �Accept-Language� header
1568*/
1569PHP_FUNCTION(locale_accept_from_http)
1570{
1571    UEnumeration *available;
1572    char *http_accept = NULL;
1573    int http_accept_len;
1574    UErrorCode status = 0;
1575    int len;
1576    char resultLocale[INTL_MAX_LOCALE_LEN+1];
1577    UAcceptResult outResult;
1578
1579    if(zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "s", &http_accept, &http_accept_len) == FAILURE)
1580    {
1581        intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR,
1582        "locale_accept_from_http: unable to parse input parameters", 0 TSRMLS_CC );
1583        RETURN_FALSE;
1584    }
1585
1586    available = ures_openAvailableLocales(NULL, &status);
1587    INTL_CHECK_STATUS(status, "locale_accept_from_http: failed to retrieve locale list");
1588    len = uloc_acceptLanguageFromHTTP(resultLocale, INTL_MAX_LOCALE_LEN,
1589                        &outResult, http_accept, available, &status);
1590    uenum_close(available);
1591    INTL_CHECK_STATUS(status, "locale_accept_from_http: failed to find acceptable locale");
1592    if (len < 0 || outResult == ULOC_ACCEPT_FAILED) {
1593        RETURN_FALSE;
1594    }
1595    RETURN_STRINGL(resultLocale, len, 1);
1596}
1597/* }}} */
1598
1599/*
1600 * Local variables:
1601 * tab-width: 4
1602 * c-basic-offset: 4
1603 * End:
1604 * vim600: noet sw=4 ts=4 fdm=marker
1605 * vim<600: noet sw=4 ts=4
1606 *can_loc_len
1607*/
1608