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: Vadim Savchuk <vsavchuk@productengine.com> |
14 | Dmitry Lakhtyuk <dlakhtyuk@productengine.com> |
15 +----------------------------------------------------------------------+
16 */
17
18 #ifdef HAVE_CONFIG_H
19 #include "config.h"
20 #endif
21
22 #include "php_intl.h"
23 #include "collator.h"
24 #include "collator_class.h"
25 #include "collator_sort.h"
26 #include "collator_convert.h"
27 #include "intl_convert.h"
28
29 #if !defined(HAVE_PTRDIFF_T) && !defined(_PTRDIFF_T_DEFINED)
30 typedef zend_long ptrdiff_t;
31 #endif
32
33 /**
34 * Declare 'index' which will point to sort key in sort key
35 * buffer.
36 */
37 typedef struct _collator_sort_key_index {
38 char* key; /* pointer to sort key */
39 zval* zstr; /* pointer to original string(hash-item) */
40 } collator_sort_key_index_t;
41
42 ZEND_EXTERN_MODULE_GLOBALS( intl )
43
44 static const size_t DEF_SORT_KEYS_BUF_SIZE = 1048576;
45 static const size_t DEF_SORT_KEYS_BUF_INCREMENT = 1048576;
46
47 static const size_t DEF_SORT_KEYS_INDX_BUF_SIZE = 1048576;
48 static const size_t DEF_SORT_KEYS_INDX_BUF_INCREMENT = 1048576;
49
50 static const size_t DEF_UTF16_BUF_SIZE = 1024;
51
52 /* {{{ collator_regular_compare_function */
collator_regular_compare_function(zval * result,zval * op1,zval * op2)53 static int collator_regular_compare_function(zval *result, zval *op1, zval *op2)
54 {
55 Collator_object* co = NULL;
56 int rc = SUCCESS;
57 zval str1, str2;
58 zval num1, num2;
59 zval norm1, norm2;
60 zval *num1_p = NULL, *num2_p = NULL;
61 zval *norm1_p = NULL, *norm2_p = NULL;
62 zval *str1_p, *str2_p;
63
64 ZVAL_NULL(&str1);
65 str1_p = collator_convert_object_to_string( op1, &str1 );
66 ZVAL_NULL(&str2);
67 str2_p = collator_convert_object_to_string( op2, &str2 );
68
69 /* If both args are strings AND either of args is not numeric string
70 * then use ICU-compare. Otherwise PHP-compare. */
71 if( Z_TYPE_P(str1_p) == IS_STRING && Z_TYPE_P(str2_p) == IS_STRING &&
72 ( str1_p == ( num1_p = collator_convert_string_to_number_if_possible( str1_p, &num1 ) ) ||
73 str2_p == ( num2_p = collator_convert_string_to_number_if_possible( str2_p, &num2 ) ) ) )
74 {
75 /* Fetch collator object. */
76 co = Z_INTL_COLLATOR_P(&INTL_G(current_collator));
77
78 if (!co || !co->ucoll) {
79 intl_error_set_code( NULL, COLLATOR_ERROR_CODE( co ) );
80 intl_errors_set_custom_msg( COLLATOR_ERROR_P( co ),
81 "Object not initialized", 0 );
82 zend_throw_error(NULL, "Object not initialized");
83 rc = FAILURE;
84 goto cleanup;
85 }
86
87 /* Compare the strings using ICU. */
88 ZVAL_LONG(result, ucol_strcoll(
89 co->ucoll,
90 INTL_Z_STRVAL_P(str1_p), INTL_Z_STRLEN_P(str1_p),
91 INTL_Z_STRVAL_P(str2_p), INTL_Z_STRLEN_P(str2_p) ));
92 }
93 else
94 {
95 /* num1 is set if str1 and str2 are strings. */
96 if( num1_p )
97 {
98 if( num1_p == str1_p )
99 {
100 /* str1 is string but not numeric string
101 * just convert it to utf8.
102 */
103 norm1_p = collator_convert_zstr_utf16_to_utf8( str1_p, &norm1 );
104
105 /* num2 is not set but str2 is string => do normalization. */
106 norm2_p = collator_normalize_sort_argument( str2_p, &norm2 );
107 }
108 else
109 {
110 /* str1 is numeric strings => passthru to PHP-compare. */
111 Z_TRY_ADDREF_P(num1_p);
112 norm1_p = num1_p;
113
114 /* str2 is numeric strings => passthru to PHP-compare. */
115 Z_TRY_ADDREF_P(num2_p);
116 norm2_p = num2_p;
117 }
118 }
119 else
120 {
121 /* num1 is not set if str1 or str2 is not a string => do normalization. */
122 norm1_p = collator_normalize_sort_argument( str1_p, &norm1 );
123
124 /* if num1 is not set then num2 is not set as well => do normalization. */
125 norm2_p = collator_normalize_sort_argument( str2_p, &norm2 );
126 }
127
128 rc = compare_function( result, norm1_p, norm2_p );
129
130 zval_ptr_dtor( norm1_p );
131 zval_ptr_dtor( norm2_p );
132 }
133
134 cleanup:
135 if( num1_p )
136 zval_ptr_dtor( num1_p );
137
138 if( num2_p )
139 zval_ptr_dtor( num2_p );
140
141 zval_ptr_dtor( str1_p );
142 zval_ptr_dtor( str2_p );
143
144 return rc;
145 }
146 /* }}} */
147
148 /* {{{ collator_numeric_compare_function
149 * Convert input args to double and compare it.
150 */
collator_numeric_compare_function(zval * result,zval * op1,zval * op2)151 static int collator_numeric_compare_function(zval *result, zval *op1, zval *op2)
152 {
153 zval num1, num2;
154 zval *num1_p = NULL;
155 zval *num2_p = NULL;
156
157 if( Z_TYPE_P(op1) == IS_STRING )
158 {
159 num1_p = collator_convert_string_to_double( op1, &num1 );
160 op1 = num1_p;
161 }
162
163 if( Z_TYPE_P(op2) == IS_STRING )
164 {
165 num2_p = collator_convert_string_to_double( op2, &num2 );
166 op2 = num2_p;
167 }
168
169 ZVAL_LONG(result, numeric_compare_function(op1, op2));
170
171 if( num1_p )
172 zval_ptr_dtor( num1_p );
173 if( num2_p )
174 zval_ptr_dtor( num2_p );
175
176 return SUCCESS;
177 }
178 /* }}} */
179
180 /* {{{ collator_icu_compare_function
181 * Direct use of ucol_strcoll.
182 */
collator_icu_compare_function(zval * result,zval * op1,zval * op2)183 static int collator_icu_compare_function(zval *result, zval *op1, zval *op2)
184 {
185 zval str1, str2;
186 int rc = SUCCESS;
187 Collator_object* co = NULL;
188 zval *str1_p = NULL;
189 zval *str2_p = NULL;
190
191 str1_p = collator_make_printable_zval( op1, &str1);
192 str2_p = collator_make_printable_zval( op2, &str2 );
193
194 /* Fetch collator object. */
195 co = Z_INTL_COLLATOR_P(&INTL_G(current_collator));
196
197 /* Compare the strings using ICU. */
198 ZVAL_LONG(result, ucol_strcoll(
199 co->ucoll,
200 INTL_Z_STRVAL_P(str1_p), INTL_Z_STRLEN_P(str1_p),
201 INTL_Z_STRVAL_P(str2_p), INTL_Z_STRLEN_P(str2_p) ));
202
203 zval_ptr_dtor( str1_p );
204 zval_ptr_dtor( str2_p );
205
206 return rc;
207 }
208 /* }}} */
209
210 /* {{{ collator_compare_func
211 * Taken from PHP7 source (array_data_compare).
212 */
collator_compare_func(const void * a,const void * b)213 static int collator_compare_func( const void* a, const void* b )
214 {
215 Bucket *f;
216 Bucket *s;
217 zval result;
218 zval *first;
219 zval *second;
220
221 f = (Bucket *) a;
222 s = (Bucket *) b;
223
224 first = &f->val;
225 second = &s->val;
226
227 if( INTL_G(compare_func)( &result, first, second) == FAILURE )
228 return 0;
229
230 if( Z_TYPE(result) == IS_DOUBLE )
231 {
232 if( Z_DVAL(result) < 0 )
233 return -1;
234 else if( Z_DVAL(result) > 0 )
235 return 1;
236 else
237 return 0;
238 }
239
240 convert_to_long(&result);
241
242 if( Z_LVAL(result) < 0 )
243 return -1;
244 else if( Z_LVAL(result) > 0 )
245 return 1;
246
247 return 0;
248 }
249 /* }}} */
250
251 /* {{{ collator_cmp_sort_keys
252 * Compare sort keys
253 */
collator_cmp_sort_keys(const void * p1,const void * p2)254 static int collator_cmp_sort_keys( const void *p1, const void *p2 )
255 {
256 char* key1 = ((collator_sort_key_index_t*)p1)->key;
257 char* key2 = ((collator_sort_key_index_t*)p2)->key;
258
259 return strcmp( key1, key2 );
260 }
261 /* }}} */
262
263 /* {{{ collator_get_compare_function
264 * Choose compare function according to sort flags.
265 */
collator_get_compare_function(const zend_long sort_flags)266 static collator_compare_func_t collator_get_compare_function( const zend_long sort_flags )
267 {
268 collator_compare_func_t func;
269
270 switch( sort_flags )
271 {
272 case COLLATOR_SORT_NUMERIC:
273 func = collator_numeric_compare_function;
274 break;
275
276 case COLLATOR_SORT_STRING:
277 func = collator_icu_compare_function;
278 break;
279
280 case COLLATOR_SORT_REGULAR:
281 default:
282 func = collator_regular_compare_function;
283 break;
284 }
285
286 return func;
287 }
288 /* }}} */
289
290 /* {{{ collator_sort_internal
291 * Common code shared by collator_sort() and collator_asort() API functions.
292 */
collator_sort_internal(int renumber,INTERNAL_FUNCTION_PARAMETERS)293 static void collator_sort_internal( int renumber, INTERNAL_FUNCTION_PARAMETERS )
294 {
295 zval saved_collator;
296 zval* array = NULL;
297 HashTable* hash = NULL;
298 zend_long sort_flags = COLLATOR_SORT_REGULAR;
299
300 COLLATOR_METHOD_INIT_VARS
301
302 /* Parse parameters. */
303 if( zend_parse_method_parameters( ZEND_NUM_ARGS(), getThis(), "Oa/|l",
304 &object, Collator_ce_ptr, &array, &sort_flags ) == FAILURE )
305 {
306 intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR,
307 "collator_sort_internal: unable to parse input params", 0 );
308
309 RETURN_FALSE;
310 }
311
312 /* Fetch the object. */
313 COLLATOR_METHOD_FETCH_OBJECT;
314
315 /* Set 'compare function' according to sort flags. */
316 INTL_G(compare_func) = collator_get_compare_function( sort_flags );
317
318 hash = Z_ARRVAL_P( array );
319
320 /* Convert strings in the specified array from UTF-8 to UTF-16. */
321 collator_convert_hash_from_utf8_to_utf16( hash, COLLATOR_ERROR_CODE_P( co ) );
322 COLLATOR_CHECK_STATUS( co, "Error converting hash from UTF-8 to UTF-16" );
323
324 /* Save specified collator in the request-global (?) variable. */
325 ZVAL_COPY_VALUE(&saved_collator, &INTL_G( current_collator ));
326 ZVAL_COPY_VALUE(&INTL_G( current_collator ), object);
327
328 /* Sort specified array. */
329 zend_hash_sort(hash, collator_compare_func, renumber);
330
331 /* Restore saved collator. */
332 ZVAL_COPY_VALUE(&INTL_G( current_collator ), &saved_collator);
333
334 /* Convert strings in the specified array back to UTF-8. */
335 collator_convert_hash_from_utf16_to_utf8( hash, COLLATOR_ERROR_CODE_P( co ) );
336 COLLATOR_CHECK_STATUS( co, "Error converting hash from UTF-16 to UTF-8" );
337
338 RETURN_TRUE;
339 }
340 /* }}} */
341
342 /* {{{ proto bool Collator::sort( Collator $coll, array(string) $arr [, int $sort_flags] )
343 * Sort array using specified collator. }}} */
344 /* {{{ proto bool collator_sort( Collator $coll, array(string) $arr [, int $sort_flags] )
345 * Sort array using specified collator.
346 */
PHP_FUNCTION(collator_sort)347 PHP_FUNCTION( collator_sort )
348 {
349 collator_sort_internal( TRUE, INTERNAL_FUNCTION_PARAM_PASSTHRU );
350 }
351 /* }}} */
352
collator_sortkey_swap(collator_sort_key_index_t * p,collator_sort_key_index_t * q)353 static void collator_sortkey_swap(collator_sort_key_index_t *p, collator_sort_key_index_t *q) /* {{{ */
354 {
355 collator_sort_key_index_t t;
356 t = *p;
357 *p = *q;
358 *q = t;
359 }
360 /* }}} */
361
362 /* {{{ proto bool Collator::sortWithSortKeys( Collator $coll, array(string) $arr )
363 * Equivalent to standard PHP sort using Collator.
364 * Uses ICU ucol_getSortKey for performance. }}} */
365 /* {{{ proto bool collator_sort_with_sort_keys( Collator $coll, array(string) $arr )
366 * Equivalent to standard PHP sort using Collator.
367 * Uses ICU ucol_getSortKey for performance.
368 */
PHP_FUNCTION(collator_sort_with_sort_keys)369 PHP_FUNCTION( collator_sort_with_sort_keys )
370 {
371 zval* array = NULL;
372 zval garbage;
373 HashTable* hash = NULL;
374 zval* hashData = NULL; /* currently processed item of input hash */
375
376 char* sortKeyBuf = NULL; /* buffer to store sort keys */
377 uint32_t sortKeyBufSize = DEF_SORT_KEYS_BUF_SIZE; /* buffer size */
378 ptrdiff_t sortKeyBufOffset = 0; /* pos in buffer to store sort key */
379 uint32_t sortKeyLen = 0; /* the length of currently processing key */
380 uint32_t bufLeft = 0;
381 uint32_t bufIncrement = 0;
382
383 collator_sort_key_index_t* sortKeyIndxBuf = NULL; /* buffer to store 'indexes' which will be passed to 'qsort' */
384 uint32_t sortKeyIndxBufSize = DEF_SORT_KEYS_INDX_BUF_SIZE;
385 uint32_t sortKeyIndxSize = sizeof( collator_sort_key_index_t );
386
387 uint32_t sortKeyCount = 0;
388 uint32_t j = 0;
389
390 UChar* utf16_buf = NULL; /* tmp buffer to hold current processing string in utf-16 */
391 int utf16_buf_size = DEF_UTF16_BUF_SIZE; /* the length of utf16_buf */
392 int utf16_len = 0; /* length of converted string */
393
394 COLLATOR_METHOD_INIT_VARS
395
396 /* Parse parameters. */
397 if( zend_parse_method_parameters( ZEND_NUM_ARGS(), getThis(), "Oa",
398 &object, Collator_ce_ptr, &array ) == FAILURE )
399 {
400 intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR,
401 "collator_sort_with_sort_keys: unable to parse input params", 0 );
402
403 RETURN_FALSE;
404 }
405
406 /* Fetch the object. */
407 COLLATOR_METHOD_FETCH_OBJECT;
408
409 if (!co || !co->ucoll) {
410 intl_error_set_code( NULL, COLLATOR_ERROR_CODE( co ) );
411 intl_errors_set_custom_msg( COLLATOR_ERROR_P( co ),
412 "Object not initialized", 0 );
413 zend_throw_error(NULL, "Object not initialized");
414
415 RETURN_FALSE;
416 }
417
418 /*
419 * Sort specified array.
420 */
421 hash = Z_ARRVAL_P( array );
422
423 if( !hash || zend_hash_num_elements( hash ) == 0 )
424 RETURN_TRUE;
425
426 /* Create bufers */
427 sortKeyBuf = ecalloc( sortKeyBufSize, sizeof( char ) );
428 sortKeyIndxBuf = ecalloc( sortKeyIndxBufSize, sizeof( uint8_t ) );
429 utf16_buf = eumalloc( utf16_buf_size );
430
431 /* Iterate through input hash and create a sort key for each value. */
432 ZEND_HASH_FOREACH_VAL(hash, hashData) {
433 /* Convert current hash item from UTF-8 to UTF-16LE and save the result to utf16_buf. */
434
435 utf16_len = utf16_buf_size;
436
437 /* Process string values only. */
438 if( Z_TYPE_P( hashData ) == IS_STRING )
439 {
440 intl_convert_utf8_to_utf16( &utf16_buf, &utf16_len, Z_STRVAL_P( hashData ), Z_STRLEN_P( hashData ), COLLATOR_ERROR_CODE_P( co ) );
441
442 if( U_FAILURE( COLLATOR_ERROR_CODE( co ) ) )
443 {
444 intl_error_set_code( NULL, COLLATOR_ERROR_CODE( co ) );
445 intl_errors_set_custom_msg( COLLATOR_ERROR_P( co ), "Sort with sort keys failed", 0 );
446
447 if( utf16_buf )
448 efree( utf16_buf );
449
450 efree( sortKeyIndxBuf );
451 efree( sortKeyBuf );
452
453 RETURN_FALSE;
454 }
455 }
456 else
457 {
458 /* Set empty string */
459 utf16_len = 0;
460 utf16_buf[utf16_len] = 0;
461 }
462
463 if( (utf16_len + 1) > utf16_buf_size )
464 utf16_buf_size = utf16_len + 1;
465
466 /* Get sort key, reallocating the buffer if needed. */
467 bufLeft = sortKeyBufSize - sortKeyBufOffset;
468
469 sortKeyLen = ucol_getSortKey( co->ucoll,
470 utf16_buf,
471 utf16_len,
472 (uint8_t*)sortKeyBuf + sortKeyBufOffset,
473 bufLeft );
474
475 /* check for sortKeyBuf overflow, increasing its size of the buffer if needed */
476 if( sortKeyLen > bufLeft )
477 {
478 bufIncrement = ( sortKeyLen > DEF_SORT_KEYS_BUF_INCREMENT ) ? sortKeyLen : DEF_SORT_KEYS_BUF_INCREMENT;
479
480 sortKeyBufSize += bufIncrement;
481 bufLeft += bufIncrement;
482
483 sortKeyBuf = erealloc( sortKeyBuf, sortKeyBufSize );
484
485 sortKeyLen = ucol_getSortKey( co->ucoll, utf16_buf, utf16_len, (uint8_t*)sortKeyBuf + sortKeyBufOffset, bufLeft );
486 }
487
488 /* check sortKeyIndxBuf overflow, increasing its size of the buffer if needed */
489 if( ( sortKeyCount + 1 ) * sortKeyIndxSize > sortKeyIndxBufSize )
490 {
491 bufIncrement = ( sortKeyIndxSize > DEF_SORT_KEYS_INDX_BUF_INCREMENT ) ? sortKeyIndxSize : DEF_SORT_KEYS_INDX_BUF_INCREMENT;
492
493 sortKeyIndxBufSize += bufIncrement;
494
495 sortKeyIndxBuf = erealloc( sortKeyIndxBuf, sortKeyIndxBufSize );
496 }
497
498 sortKeyIndxBuf[sortKeyCount].key = (char*)sortKeyBufOffset; /* remember just offset, cause address */
499 /* of 'sortKeyBuf' may be changed due to realloc. */
500 sortKeyIndxBuf[sortKeyCount].zstr = hashData;
501
502 sortKeyBufOffset += sortKeyLen;
503 ++sortKeyCount;
504
505 } ZEND_HASH_FOREACH_END();
506
507 /* update ptrs to point to valid keys. */
508 for( j = 0; j < sortKeyCount; j++ )
509 sortKeyIndxBuf[j].key = sortKeyBuf + (ptrdiff_t)sortKeyIndxBuf[j].key;
510
511 /* sort it */
512 zend_sort( sortKeyIndxBuf, sortKeyCount,
513 sortKeyIndxSize, collator_cmp_sort_keys, (swap_func_t)collator_sortkey_swap);
514
515 ZVAL_COPY_VALUE(&garbage, array);
516 /* for resulting hash we'll assign new hash keys rather then reordering */
517 array_init(array);
518
519 for( j = 0; j < sortKeyCount; j++ )
520 {
521 Z_TRY_ADDREF_P( sortKeyIndxBuf[j].zstr );
522 zend_hash_next_index_insert( Z_ARRVAL_P(array), sortKeyIndxBuf[j].zstr);
523 }
524
525 if( utf16_buf )
526 efree( utf16_buf );
527
528 zval_ptr_dtor(&garbage);
529 efree( sortKeyIndxBuf );
530 efree( sortKeyBuf );
531
532 RETURN_TRUE;
533 }
534 /* }}} */
535
536 /* {{{ proto bool Collator::asort( Collator $coll, array(string) $arr )
537 * Sort array using specified collator, maintaining index association. }}} */
538 /* {{{ proto bool collator_asort( Collator $coll, array(string) $arr )
539 * Sort array using specified collator, maintaining index association.
540 */
PHP_FUNCTION(collator_asort)541 PHP_FUNCTION( collator_asort )
542 {
543 collator_sort_internal( FALSE, INTERNAL_FUNCTION_PARAM_PASSTHRU );
544 }
545 /* }}} */
546
547 /* {{{ proto bool Collator::getSortKey( Collator $coll, string $str )
548 * Get a sort key for a string from a Collator. }}} */
549 /* {{{ proto bool collator_get_sort_key( Collator $coll, string $str )
550 * Get a sort key for a string from a Collator. */
PHP_FUNCTION(collator_get_sort_key)551 PHP_FUNCTION( collator_get_sort_key )
552 {
553 char* str = NULL;
554 size_t str_len = 0;
555 UChar* ustr = NULL;
556 int32_t ustr_len = 0;
557 int key_len = 0;
558 zend_string* key_str;
559
560 COLLATOR_METHOD_INIT_VARS
561
562 /* Parse parameters. */
563 if( zend_parse_method_parameters( ZEND_NUM_ARGS(), getThis(), "Os",
564 &object, Collator_ce_ptr, &str, &str_len ) == FAILURE )
565 {
566 intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR,
567 "collator_get_sort_key: unable to parse input params", 0 );
568
569 RETURN_FALSE;
570 }
571
572 /* Fetch the object. */
573 COLLATOR_METHOD_FETCH_OBJECT;
574
575 if (!co || !co->ucoll) {
576 intl_error_set_code( NULL, COLLATOR_ERROR_CODE( co ) );
577 intl_errors_set_custom_msg( COLLATOR_ERROR_P( co ),
578 "Object not initialized", 0 );
579 zend_throw_error(NULL, "Object not initialized");
580
581 RETURN_FALSE;
582 }
583
584 /*
585 * Compare given strings (converting them to UTF-16 first).
586 */
587
588 /* First convert the strings to UTF-16. */
589 intl_convert_utf8_to_utf16(
590 &ustr, &ustr_len, str, str_len, COLLATOR_ERROR_CODE_P( co ) );
591 if( U_FAILURE( COLLATOR_ERROR_CODE( co ) ) )
592 {
593 /* Set global error code. */
594 intl_error_set_code( NULL, COLLATOR_ERROR_CODE( co ) );
595
596 /* Set error messages. */
597 intl_errors_set_custom_msg( COLLATOR_ERROR_P( co ),
598 "Error converting first argument to UTF-16", 0 );
599 efree( ustr );
600 RETURN_FALSE;
601 }
602
603 /* ucol_getSortKey is exception in that the key length includes the
604 * NUL terminator*/
605 key_len = ucol_getSortKey(co->ucoll, ustr, ustr_len, NULL, 0);
606 if(!key_len) {
607 efree( ustr );
608 RETURN_FALSE;
609 }
610 key_str = zend_string_alloc(key_len, 0);
611 key_len = ucol_getSortKey(co->ucoll, ustr, ustr_len, (uint8_t*)ZSTR_VAL(key_str), key_len);
612 efree( ustr );
613 if(!key_len) {
614 RETURN_FALSE;
615 }
616 ZSTR_LEN(key_str) = key_len - 1;
617 RETVAL_NEW_STR(key_str);
618 }
619 /* }}} */
620
621 /*
622 * Local variables:
623 * tab-width: 4
624 * c-basic-offset: 4
625 * End:
626 * vim600: noet sw=4 ts=4 fdm=marker
627 * vim<600: noet sw=4 ts=4
628 */
629