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
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(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*/
148static 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*/
178static 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 */
212PHP_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 */
224PHP_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*/
259static 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*/
380static 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 */
443PHP_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 */
455PHP_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 */
467PHP_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 }}} */
477static 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*/
617PHP_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*/
629PHP_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*/
641PHP_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*/
653PHP_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*/
667PHP_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 */
681PHP_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 */
760PHP_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*/
772static 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*/
797static 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*/
813static 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*/
887static 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* }}} */
907PHP_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*/
992static 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*/
1037static 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*/
1102PHP_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*/
1146PHP_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*/
1202static 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*/
1239PHP_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
1403static 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*/
1419static 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*/
1531PHP_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		if(fallback_loc_str) {
1552			loc_range = ZSTR_VAL(fallback_loc_str);
1553		} else {
1554			loc_range = intl_locale_get_default();
1555		}
1556	}
1557
1558	hash_arr = Z_ARRVAL_P(arr);
1559
1560	if( !hash_arr || zend_hash_num_elements( hash_arr ) == 0 ) {
1561		RETURN_EMPTY_STRING();
1562	}
1563
1564	result_str = lookup_loc_range(loc_range, hash_arr, boolCanonical);
1565	if(result_str == NULL || ZSTR_VAL(result_str)[0] == '\0') {
1566		if( fallback_loc_str ) {
1567			result_str = zend_string_copy(fallback_loc_str);
1568		} else {
1569			RETURN_EMPTY_STRING();
1570		}
1571	}
1572
1573	RETURN_STR(result_str);
1574}
1575/* }}} */
1576
1577/* {{{ proto string Locale::acceptFromHttp(string $http_accept)
1578* Tries to find out best available locale based on HTTP �Accept-Language� header
1579*/
1580/* }}} */
1581/* {{{ proto string locale_accept_from_http(string $http_accept)
1582* Tries to find out best available locale based on HTTP �Accept-Language� header
1583*/
1584PHP_FUNCTION(locale_accept_from_http)
1585{
1586	UEnumeration *available;
1587	char *http_accept = NULL;
1588	size_t http_accept_len;
1589	UErrorCode status = 0;
1590	int len;
1591	char resultLocale[INTL_MAX_LOCALE_LEN+1];
1592	UAcceptResult outResult;
1593
1594	if(zend_parse_parameters( ZEND_NUM_ARGS(), "s", &http_accept, &http_accept_len) == FAILURE)
1595	{
1596		intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR,
1597		"locale_accept_from_http: unable to parse input parameters", 0 );
1598		RETURN_FALSE;
1599	}
1600
1601	available = ures_openAvailableLocales(NULL, &status);
1602	INTL_CHECK_STATUS(status, "locale_accept_from_http: failed to retrieve locale list");
1603	len = uloc_acceptLanguageFromHTTP(resultLocale, INTL_MAX_LOCALE_LEN,
1604						&outResult, http_accept, available, &status);
1605	uenum_close(available);
1606	INTL_CHECK_STATUS(status, "locale_accept_from_http: failed to find acceptable locale");
1607	if (len < 0 || outResult == ULOC_ACCEPT_FAILED) {
1608		RETURN_FALSE;
1609	}
1610	RETURN_STRINGL(resultLocale, len);
1611}
1612/* }}} */
1613
1614/*
1615 * Local variables:
1616 * tab-width: 4
1617 * c-basic-offset: 4
1618 * End:
1619 * vim600: noet sw=4 ts=4 fdm=marker
1620 * vim<600: noet sw=4 ts=4
1621 *can_loc_len
1622*/
1623