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

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

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