locale_methods.c 46 KB

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