diff --git a/ext/intl/grapheme/grapheme.c b/ext/intl/grapheme/grapheme.c new file mode 100644 index 000000000000..967b0e3d6596 --- /dev/null +++ b/ext/intl/grapheme/grapheme.c @@ -0,0 +1,448 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Rui Hirokawa | + | Stanislav Malyshev | + | Kirti Velankar | + | sepehr mahmoodi | + +----------------------------------------------------------------------+ +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include "grapheme_util.h" +#include "grapheme_arginfo.h" +#include "grapheme_mask.h" + +/* {{{ grapheme_functions[] */ +static const zend_function_entry grapheme_functions[] = { + PHP_FE(grapheme_strlen, arginfo_grapheme_strlen) + PHP_FE(grapheme_strpos, arginfo_grapheme_strpos) + PHP_FE(grapheme_stripos, arginfo_grapheme_stripos) + PHP_FE(grapheme_strrpos, arginfo_grapheme_strrpos) + PHP_FE(grapheme_strripos, arginfo_grapheme_strripos) + PHP_FE(grapheme_substr, arginfo_grapheme_substr) + PHP_FE(grapheme_strstr, arginfo_grapheme_strstr) + PHP_FE(grapheme_stristr, arginfo_grapheme_stristr) + PHP_FE(grapheme_extract, arginfo_grapheme_extract) + PHP_FE(grapheme_mask, arginfo_grapheme_mask) + PHP_FE_END +}; +/* }}} */ + +/* {{{ PHP_MINIT_FUNCTION */ +PHP_MINIT_FUNCTION(grapheme) +{ + return SUCCESS; +} +/* }}} */ + +/* {{{ PHP_MSHUTDOWN_FUNCTION */ +PHP_MSHUTDOWN_FUNCTION(grapheme) +{ + return SUCCESS; +} +/* }}} */ + +/* {{{ PHP_MINFO_FUNCTION */ +PHP_MINFO_FUNCTION(grapheme) +{ + php_info_print_table_start(); + php_info_print_table_header(2, "Grapheme Support", "enabled"); + php_info_print_table_end(); +} +/* }}} */ + +/* {{{ PHP_RSHUTDOWN_FUNCTION */ +PHP_RSHUTDOWN_FUNCTION(grapheme) +{ + return SUCCESS; +} +/* }}} */ + +/* {{{ grapheme_module_entry */ +zend_module_entry grapheme_module_entry = { + STANDARD_MODULE_HEADER, + "grapheme", + grapheme_functions, + PHP_MINIT(grapheme), + PHP_MSHUTDOWN(grapheme), + NULL, + PHP_RSHUTDOWN(grapheme), + PHP_MINFO(grapheme), + PHP_GRAPHEME_VERSION, + STANDARD_MODULE_PROPERTIES +}; +/* }}} */ + +#ifdef COMPILE_DL_GRAPHEME +ZEND_GET_MODULE(grapheme) +#endif + +/* {{{ Returns the number of grapheme clusters in a string */ +PHP_FUNCTION(grapheme_strlen) +{ + const char *str; + size_t str_len; + UText ut = UTEXT_INITIALIZER; + int32_t grapheme_len; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_STRING(str, str_len) + ZEND_PARSE_PARAMETERS_END(); + + UText *utxt = grapheme_open_utext(&ut, str, str_len); + if (utxt == NULL) { + RETURN_FALSE; + } + + grapheme_len = grapheme_count_graphemes(utxt); + utext_close(utxt); + + if (grapheme_len < 0) { + RETURN_FALSE; + } + + RETURN_LONG(grapheme_len); +} +/* }}} */ + +/* {{{ Find position (in grapheme units) of first occurrence of a string */ +PHP_FUNCTION(grapheme_strpos) +{ + const char *haystack, *needle; + size_t haystack_len, needle_len; + zend_long offset = 0; + int32_t pos; + + ZEND_PARSE_PARAMETERS_START(2, 3) + Z_PARAM_STRING(haystack, haystack_len) + Z_PARAM_STRING(needle, needle_len) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(offset) + ZEND_PARSE_PARAMETERS_END(); + + if (needle_len == 0) { + php_error_docref(NULL, E_WARNING, "Empty needle"); + RETURN_FALSE; + } + + pos = grapheme_strpos_utf8(haystack, haystack_len, needle, needle_len, offset, NULL); + if (pos < 0) { + RETURN_FALSE; + } + + RETURN_LONG(pos); +} +/* }}} */ + +/* {{{ Finds position (in grapheme units) of first occurrence of a string (case-insensitive) */ +PHP_FUNCTION(grapheme_stripos) +{ + const char *haystack, *needle; + size_t haystack_len, needle_len; + zend_long offset = 0; + int32_t pos; + + ZEND_PARSE_PARAMETERS_START(2, 3) + Z_PARAM_STRING(haystack, haystack_len) + Z_PARAM_STRING(needle, needle_len) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(offset) + ZEND_PARSE_PARAMETERS_END(); + + if (needle_len == 0) { + php_error_docref(NULL, E_WARNING, "Empty needle"); + RETURN_FALSE; + } + + pos = grapheme_stripos_utf8(haystack, haystack_len, needle, needle_len, offset, NULL); + if (pos < 0) { + RETURN_FALSE; + } + + RETURN_LONG(pos); +} +/* }}} */ + +/* {{{ Find position (in grapheme units) of last occurrence of a string */ +PHP_FUNCTION(grapheme_strrpos) +{ + const char *haystack, *needle; + size_t haystack_len, needle_len; + zend_long offset = 0; + int32_t pos; + + ZEND_PARSE_PARAMETERS_START(2, 3) + Z_PARAM_STRING(haystack, haystack_len) + Z_PARAM_STRING(needle, needle_len) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(offset) + ZEND_PARSE_PARAMETERS_END(); + + if (needle_len == 0) { + php_error_docref(NULL, E_WARNING, "Empty needle"); + RETURN_FALSE; + } + + pos = grapheme_strrpos_utf8(haystack, haystack_len, needle, needle_len, offset, NULL); + if (pos < 0) { + RETURN_FALSE; + } + + RETURN_LONG(pos); +} +/* }}} */ + +/* {{{ Finds position (in grapheme units) of last occurrence of a string (case-insensitive) */ +PHP_FUNCTION(grapheme_strripos) +{ + const char *haystack, *needle; + size_t haystack_len, needle_len; + zend_long offset = 0; + int32_t pos; + + ZEND_PARSE_PARAMETERS_START(2, 3) + Z_PARAM_STRING(haystack, haystack_len) + Z_PARAM_STRING(needle, needle_len) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(offset) + ZEND_PARSE_PARAMETERS_END(); + + if (needle_len == 0) { + php_error_docref(NULL, E_WARNING, "Empty needle"); + RETURN_FALSE; + } + + pos = grapheme_strripos_utf8(haystack, haystack_len, needle, needle_len, offset, NULL); + if (pos < 0) { + RETURN_FALSE; + } + + RETURN_LONG(pos); +} +/* }}} */ + +/* {{{ Returns part of a string */ +PHP_FUNCTION(grapheme_substr) +{ + const char *str; + size_t str_len; + zend_long offset = 0, length = 0; + zend_bool length_is_null = 1; + const char *p; + int32_t (*iter)(UText *); + UText ut = UTEXT_INITIALIZER; + int32_t start_pos, end_pos, grapheme_len, i; + char *out; + size_t out_len; + + ZEND_PARSE_PARAMETERS_START(2, 4) + Z_PARAM_STRING(str, str_len) + Z_PARAM_LONG(offset) + Z_PARAM_OPTIONAL + Z_PARAM_LONG_OR_NULL(length, length_is_null) + ZEND_PARSE_PARAMETERS_END(); + + if (str_len == 0) { + RETURN_FALSE; + } + + if (length_is_null) { + length = GRAPHEME_STRPOS_UTF8_MAXLEN; + } + + UText *utxt = grapheme_open_utext(&ut, str, str_len); + if (utxt == NULL) { + RETURN_FALSE; + } + + grapheme_len = grapheme_count_graphemes(utxt); + if (grapheme_len <= 0) { + utext_close(utxt); + RETURN_FALSE; + } + + /* normalize offset */ + if (offset < 0) { + offset += grapheme_len; + } + + if (offset < 0 || offset >= grapheme_len) { + utext_close(utxt); + RETURN_FALSE; + } + + /* calculate end position */ + if (length == 0) { + end_pos = offset; + } else if (length > 0) { + end_pos = offset + length; + if (end_pos > grapheme_len) { + end_pos = grapheme_len; + } + } else { + end_pos = grapheme_len + length; + if (end_pos <= offset) { + utext_close(utxt); + RETURN_FALSE; + } + } + + /* find start byte position */ + start_pos = 0; + if (offset > 0) { + start_pos = grapheme_strpos_utf8(str, str_len, offset, NULL); + if (start_pos < 0) { + utext_close(utxt); + RETURN_FALSE; + } + } + + /* find end byte position */ + if (end_pos == offset) { + out_len = 0; + } else { + int32_t byte_len = grapheme_strpos_utf8(str, str_len, end_pos - offset, str + start_pos); + if (byte_len < 0) { + utext_close(utxt); + RETURN_FALSE; + } + out_len = byte_len; + } + + utext_close(utxt); + + if (out_len == 0) { + RETURN_EMPTY_STRING(); + } + + out = estrndup(str + start_pos, out_len); + out[out_len] = '\0'; + RETURN_STRINGL(out, out_len); +} +/* }}} */ + +/* {{{ Returns part of haystack string from the first occurrence of needle to the end */ +PHP_FUNCTION(grapheme_strstr) +{ + const char *haystack, *needle; + size_t haystack_len, needle_len; + zend_bool before_needle = 0; + int32_t pos; + + ZEND_PARSE_PARAMETERS_START(2, 3) + Z_PARAM_STRING(haystack, haystack_len) + Z_PARAM_STRING(needle, needle_len) + Z_PARAM_OPTIONAL + Z_PARAM_BOOL(before_needle) + ZEND_PARSE_PARAMETERS_END(); + + if (needle_len == 0) { + php_error_docref(NULL, E_WARNING, "Empty needle"); + RETURN_FALSE; + } + + pos = grapheme_strpos_utf8(haystack, haystack_len, needle, needle_len, 0, NULL); + if (pos < 0) { + RETURN_FALSE; + } + + if (before_needle) { + RETURN_STRINGL(haystack, pos); + } else { + RETURN_STRINGL(haystack + pos, haystack_len - pos); + } +} +/* }}} */ + +/* {{{ Returns part of haystack string from the first occurrence of needle (case-insensitive) to the end */ +PHP_FUNCTION(grapheme_stristr) +{ + const char *haystack, *needle; + size_t haystack_len, needle_len; + zend_bool before_needle = 0; + int32_t pos; + + ZEND_PARSE_PARAMETERS_START(2, 3) + Z_PARAM_STRING(haystack, haystack_len) + Z_PARAM_STRING(needle, needle_len) + Z_PARAM_OPTIONAL + Z_PARAM_BOOL(before_needle) + ZEND_PARSE_PARAMETERS_END(); + + if (needle_len == 0) { + php_error_docref(NULL, E_WARNING, "Empty needle"); + RETURN_FALSE; + } + + pos = grapheme_stripos_utf8(haystack, haystack_len, needle, needle_len, 0, NULL); + if (pos < 0) { + RETURN_FALSE; + } + + if (before_needle) { + RETURN_STRINGL(haystack, pos); + } else { + RETURN_STRINGL(haystack + pos, haystack_len - pos); + } +} +/* }}} */ + +/* {{{ Function to extract a sequence of grapheme clusters from a text buffer */ +PHP_FUNCTION(grapheme_extract) +{ + const char *str; + size_t str_len; + zend_long size, extract_type = GRAPHEME_EXTRACT_TYPE_COUNT; + zend_long start = 0; + zend_long *next = NULL; + zend_long next_pos; + int32_t extracted_len; + char *out; + + ZEND_PARSE_PARAMETERS_START(2, 5) + Z_PARAM_STRING(str, str_len) + Z_PARAM_LONG(size) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(extract_type) + Z_PARAM_LONG(start) + Z_PARAM_LONG_OR_NULL(next, 1) + ZEND_PARSE_PARAMETERS_END(); + + if (size <= 0) { + php_error_docref(NULL, E_WARNING, "Size must be positive"); + RETURN_FALSE; + } + + if (start < 0 || (size_t)start >= str_len) { + php_error_docref(NULL, E_WARNING, "Start not contained in string"); + RETURN_FALSE; + } + + extracted_len = grapheme_extract_utf8(str, str_len, size, extract_type, start, &next_pos); + if (extracted_len < 0) { + RETURN_FALSE; + } + + if (next) { + *next = next_pos; + } + + out = estrndup(str + start, extracted_len); + out[extracted_len] = '\0'; + RETURN_STRINGL(out, extracted_len); +} +/* }}} */ diff --git a/ext/intl/grapheme/grapheme.h b/ext/intl/grapheme/grapheme.h index b52f48f9d142..eb8b7e609b8e 100644 --- a/ext/intl/grapheme/grapheme.h +++ b/ext/intl/grapheme/grapheme.h @@ -8,28 +8,1184 @@ | | | SPDX-License-Identifier: BSD-3-Clause | +----------------------------------------------------------------------+ - | Authors: Ed Batutis | + | Author: Ed Batutis | +----------------------------------------------------------------------+ */ -#ifndef GRAPHEME_GRAPHEME_H -#define GRAPHEME_GRAPHEME_H +/* {{{ includes */ +#ifdef HAVE_CONFIG_H +#include +#endif + +#if __cplusplus >= 201703L +#include +#include +#endif +extern "C" { #include +#include "grapheme.h" +#include "grapheme_util.h" +} + #include +#include +#include +#include +#include -#ifdef __cplusplus -extern "C" { -#endif -void grapheme_close_global_iterator( void ); -#ifdef __cplusplus +/* }}} */ + +/* {{{ Get number of graphemes in a string */ +U_CFUNC PHP_FUNCTION(grapheme_strlen) +{ + char* string; + size_t string_len; + UChar* ustring = nullptr; + int ustring_len = 0; + zend_long ret_len; + UErrorCode status; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_STRING(string, string_len) + ZEND_PARSE_PARAMETERS_END(); + + ret_len = grapheme_ascii_check((unsigned char *)string, string_len); + + if ( ret_len >= 0 ) + RETURN_LONG(string_len); + + /* convert the string to UTF-16. */ + status = U_ZERO_ERROR; + intl_convert_utf8_to_utf16(&ustring, &ustring_len, string, string_len, &status ); + + if ( U_FAILURE( status ) ) { + /* Set global error code. */ + intl_error_set_code( nullptr, status ); + + /* Set error messages. */ + intl_error_set_custom_msg( nullptr, "Error converting input string to UTF-16"); + if (ustring) { + efree( ustring ); + } + RETURN_NULL(); + } + + ret_len = grapheme_split_string(ustring, ustring_len, nullptr, 0 ); + + if (ustring) { + efree( ustring ); + } + + if (ret_len >= 0) { + RETVAL_LONG(ret_len); + } else { + RETVAL_FALSE; + } } -#endif +/* }}} */ + +/* {{{ Find position of first occurrence of a string within another */ +U_CFUNC PHP_FUNCTION(grapheme_strpos) +{ + char *haystack, *needle, *locale = ""; + size_t haystack_len, needle_len, locale_len = 0; + const char *found; + zend_long loffset = 0; + int32_t offset = 0; + size_t noffset = 0; + zend_long ret_pos; + + ZEND_PARSE_PARAMETERS_START(2, 4) + Z_PARAM_STRING(haystack, haystack_len) + Z_PARAM_STRING(needle, needle_len) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(loffset) + Z_PARAM_PATH(locale, locale_len) + ZEND_PARSE_PARAMETERS_END(); + + if ( OUTSIDE_STRING(loffset, haystack_len) ) { + zend_argument_value_error(3, "must be contained in argument #1 ($haystack)"); + RETURN_THROWS(); + } + + /* we checked that it will fit: */ + offset = (int32_t) loffset; + noffset = offset >= 0 ? offset : (int32_t)haystack_len + offset; + + /* the offset is 'grapheme count offset' so it still might be invalid - we'll check it later */ + + if (offset >= 0 && grapheme_ascii_check((unsigned char *)haystack, haystack_len) >= 0) { + /* quick check to see if the string might be there + * I realize that 'offset' is 'grapheme count offset' but will work in spite of that + */ + found = php_memnstr(haystack + noffset, needle, needle_len, haystack + haystack_len); + + /* if it isn't there the we are done */ + if (found) { + RETURN_LONG(found - haystack); + } + RETURN_FALSE; + } + + /* do utf16 part of the strpos */ + ret_pos = grapheme_strpos_utf16(haystack, haystack_len, needle, needle_len, offset, nullptr, /* fIgnoreCase */ 0, /* last */ 0, locale); + + if ( ret_pos >= 0 ) { + RETURN_LONG(ret_pos); + } else { + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ Find position of first occurrence of a string within another, ignoring case differences */ +U_CFUNC PHP_FUNCTION(grapheme_stripos) +{ + char *haystack, *needle, *locale = ""; + size_t haystack_len, needle_len, locale_len = 0; + const char *found; + zend_long loffset = 0; + int32_t offset = 0; + zend_long ret_pos; + int is_ascii; + + ZEND_PARSE_PARAMETERS_START(2, 4) + Z_PARAM_STRING(haystack, haystack_len) + Z_PARAM_STRING(needle, needle_len) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(loffset) + Z_PARAM_PATH(locale, locale_len) + ZEND_PARSE_PARAMETERS_END(); + + if ( OUTSIDE_STRING(loffset, haystack_len) ) { + zend_argument_value_error(3, "must be contained in argument #1 ($haystack)"); + RETURN_THROWS(); + } + + /* we checked that it will fit: */ + offset = (int32_t) loffset; + + /* the offset is 'grapheme count offset' so it still might be invalid - we'll check it later */ + + is_ascii = ( grapheme_ascii_check((unsigned char*)haystack, haystack_len) >= 0 ); + + if ( is_ascii ) { + char *haystack_dup, *needle_dup; + int32_t noffset = offset >= 0 ? offset : (int32_t)haystack_len + offset; + needle_dup = estrndup(needle, needle_len); + zend_str_tolower(needle_dup, needle_len); + haystack_dup = estrndup(haystack, haystack_len); + zend_str_tolower(haystack_dup, haystack_len); + + found = php_memnstr(haystack_dup + noffset, needle_dup, needle_len, haystack_dup + haystack_len); + + efree(haystack_dup); + efree(needle_dup); + + if (found) { + RETURN_LONG(found - haystack_dup); + } + + /* if needle was ascii too, we are all done, otherwise we need to try using Unicode to see what we get */ + if ( grapheme_ascii_check((unsigned char *)needle, needle_len) >= 0 ) { + RETURN_FALSE; + } + } + + /* do utf16 part of the strpos */ + ret_pos = grapheme_strpos_utf16(haystack, haystack_len, needle, needle_len, offset, nullptr, /* fIgnoreCase */ 1, /*last */ 0, locale); + + if ( ret_pos >= 0 ) { + RETURN_LONG(ret_pos); + } else { + RETURN_FALSE; + } + +} +/* }}} */ + +/* {{{ Find position of last occurrence of a string within another */ +U_CFUNC PHP_FUNCTION(grapheme_strrpos) +{ + char *haystack, *needle; + char *locale = ""; + size_t haystack_len, needle_len, locale_len = 0; + zend_long loffset = 0; + int32_t offset = 0; + zend_long ret_pos; + int is_ascii; + + ZEND_PARSE_PARAMETERS_START(2, 4) + Z_PARAM_STRING(haystack, haystack_len) + Z_PARAM_STRING(needle, needle_len) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(loffset) + Z_PARAM_PATH(locale, locale_len) + ZEND_PARSE_PARAMETERS_END(); + + if ( OUTSIDE_STRING(loffset, haystack_len) ) { + zend_argument_value_error(3, "must be contained in argument #1 ($haystack)"); + RETURN_THROWS(); + } + + /* we checked that it will fit: */ + offset = (int32_t) loffset; + + /* the offset is 'grapheme count offset' so it still might be invalid - we'll check it later */ + + is_ascii = grapheme_ascii_check((unsigned char *)haystack, haystack_len) >= 0; + + if ( is_ascii ) { + + ret_pos = grapheme_strrpos_ascii(haystack, haystack_len, needle, needle_len, offset); + + if ( ret_pos >= 0 ) { + RETURN_LONG(ret_pos); + } + + /* if the needle was ascii too, we are done */ + + if ( grapheme_ascii_check((unsigned char *)needle, needle_len) >= 0 ) { + RETURN_FALSE; + } + + /* else we need to continue via utf16 */ + } + + ret_pos = grapheme_strpos_utf16(haystack, haystack_len, needle, needle_len, offset, nullptr, /* f_ignore_case */ 0, /* last */ 1, locale); + + if ( ret_pos >= 0 ) { + RETURN_LONG(ret_pos); + } else { + RETURN_FALSE; + } + + +} +/* }}} */ + +/* {{{ Find position of last occurrence of a string within another, ignoring case */ +U_CFUNC PHP_FUNCTION(grapheme_strripos) +{ + char *haystack, *needle, *locale = ""; + size_t haystack_len, needle_len, locale_len = 0; + zend_long loffset = 0; + int32_t offset = 0; + zend_long ret_pos; + int is_ascii; + + ZEND_PARSE_PARAMETERS_START(2, 4) + Z_PARAM_STRING(haystack, haystack_len) + Z_PARAM_STRING(needle, needle_len) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(loffset) + Z_PARAM_PATH(locale, locale_len) + ZEND_PARSE_PARAMETERS_END(); + + if ( OUTSIDE_STRING(loffset, haystack_len) ) { + zend_argument_value_error(3, "must be contained in argument #1 ($haystack)"); + RETURN_THROWS(); + } + + /* we checked that it will fit: */ + offset = (int32_t) loffset; + + /* the offset is 'grapheme count offset' so it still might be invalid - we'll check it later */ + + is_ascii = grapheme_ascii_check((unsigned char *)haystack, haystack_len) >= 0; + + if ( is_ascii ) { + char *needle_dup, *haystack_dup; + + needle_dup = estrndup(needle, needle_len); + zend_str_tolower(needle_dup, needle_len); + haystack_dup = estrndup(haystack, haystack_len); + zend_str_tolower(haystack_dup, haystack_len); + + ret_pos = grapheme_strrpos_ascii(haystack_dup, haystack_len, needle_dup, needle_len, offset); + + efree(haystack_dup); + efree(needle_dup); + + if ( ret_pos >= 0 ) { + RETURN_LONG(ret_pos); + } + + /* if the needle was ascii too, we are done */ + + if ( grapheme_ascii_check((unsigned char *)needle, needle_len) >= 0 ) { + RETURN_FALSE; + } + + /* else we need to continue via utf16 */ + } + + ret_pos = grapheme_strpos_utf16(haystack, haystack_len, needle, needle_len, offset, nullptr, /* f_ignore_case */ 1, /*last */ 1, locale); + + if ( ret_pos >= 0 ) { + RETURN_LONG(ret_pos); + } else { + RETURN_FALSE; + } + + +} +/* }}} */ + +/* {{{ Returns part of a string */ +U_CFUNC PHP_FUNCTION(grapheme_substr) +{ + char *str, *locale = ""; + zend_string *u8_sub_str; + UChar *ustr; + size_t str_len, locale_len = 0; + int32_t ustr_len; + zend_long lstart = 0, length = 0; + int32_t start = 0; + int iter_val; + UErrorCode status; + UBreakIterator* bi = nullptr; + int sub_str_start_pos, sub_str_end_pos; + int32_t (*iter_func)(UBreakIterator *); + bool no_length = true; + + ZEND_PARSE_PARAMETERS_START(2, 4) + Z_PARAM_STRING(str, str_len) + Z_PARAM_LONG(lstart) + Z_PARAM_OPTIONAL + Z_PARAM_LONG_OR_NULL(length, no_length) + Z_PARAM_PATH(locale, locale_len) + ZEND_PARSE_PARAMETERS_END(); + + if (lstart < INT32_MIN || lstart > INT32_MAX) { + zend_argument_value_error(2, "is too large"); + RETURN_THROWS(); + } + + start = (int32_t) lstart; + + if (no_length) { + length = str_len; + } + + if (length < INT32_MIN || length > INT32_MAX) { + zend_argument_value_error(3, "is too large"); + RETURN_THROWS(); + } + + /* the offset is 'grapheme count offset' so it still might be invalid - we'll check it later */ + + if ( grapheme_ascii_check((unsigned char *)str, str_len) >= 0 ) { + int32_t asub_str_len; + char *sub_str; + grapheme_substr_ascii(str, str_len, start, (int32_t)length, &sub_str, &asub_str_len); + + if ( nullptr == sub_str ) { + intl_error_set( nullptr, U_ILLEGAL_ARGUMENT_ERROR, "invalid parameters"); + RETURN_FALSE; + } + + RETURN_STRINGL(sub_str, asub_str_len); + } + + ustr = nullptr; + ustr_len = 0; + status = U_ZERO_ERROR; + intl_convert_utf8_to_utf16(&ustr, &ustr_len, str, str_len, &status); + + if ( U_FAILURE( status ) ) { + /* Set global error code. */ + intl_error_set_code( nullptr, status ); + + /* Set error messages. */ + intl_error_set_custom_msg( nullptr, "Error converting input string to UTF-16"); + if (ustr) { + efree( ustr ); + } + RETURN_FALSE; + } + + bi = grapheme_get_break_iterator(&status); + + if( U_FAILURE(status) ) { + RETURN_FALSE; + } + + ubrk_setText(bi, ustr, ustr_len, &status); + + if ( start < 0 ) { + iter_func = ubrk_previous; + ubrk_last(bi); + iter_val = 1; + } + else { + iter_func = ubrk_next; + iter_val = -1; + } + + sub_str_start_pos = 0; + + while ( start ) { + sub_str_start_pos = iter_func(bi); + + if ( UBRK_DONE == sub_str_start_pos ) { + break; + } + + start += iter_val; + } + + if (0 != start) { + if (start > 0) { + if (ustr) { + efree(ustr); + } + ubrk_close(bi); + RETURN_EMPTY_STRING(); + } + + sub_str_start_pos = 0; + ubrk_first(bi); + } + + /* OK to convert here since if str_len were big, convert above would fail */ + if (length >= (int32_t)str_len) { + + /* no length supplied or length is too big, return the rest of the string */ + + status = U_ZERO_ERROR; + u8_sub_str = intl_convert_utf16_to_utf8(ustr + sub_str_start_pos, ustr_len - sub_str_start_pos, &status); + + if (ustr) { + efree( ustr ); + } + ubrk_close( bi ); + + if ( !u8_sub_str ) { + /* Set global error code. */ + intl_error_set_code( nullptr, status ); + + /* Set error messages. */ + intl_error_set_custom_msg( nullptr, "Error converting output string to UTF-8"); + + RETURN_FALSE; + } + + /* return the allocated string, not a duplicate */ + RETVAL_NEW_STR(u8_sub_str); + return; + } + + if(length == 0) { + /* empty length - we've validated start, we can return "" now */ + if (ustr) { + efree(ustr); + } + ubrk_close(bi); + RETURN_EMPTY_STRING(); + } + + /* find the end point of the string to return */ + + if ( length < 0 ) { + iter_func = ubrk_previous; + ubrk_last(bi); + iter_val = 1; + } + else { + iter_func = ubrk_next; + iter_val = -1; + } + + sub_str_end_pos = 0; + + while ( length ) { + sub_str_end_pos = iter_func(bi); + + if ( UBRK_DONE == sub_str_end_pos ) { + break; + } + + length += iter_val; + } + + ubrk_close(bi); + + if ( UBRK_DONE == sub_str_end_pos) { + if (length < 0) { + efree(ustr); + RETURN_EMPTY_STRING(); + } else { + sub_str_end_pos = ustr_len; + } + } + + if (sub_str_start_pos > sub_str_end_pos) { + efree(ustr); + RETURN_EMPTY_STRING(); + } + + status = U_ZERO_ERROR; + u8_sub_str = intl_convert_utf16_to_utf8(ustr + sub_str_start_pos, ( sub_str_end_pos - sub_str_start_pos ), &status); + + efree( ustr ); + + if ( !u8_sub_str ) { + /* Set global error code. */ + intl_error_set_code( nullptr, status ); + + /* Set error messages. */ + intl_error_set_custom_msg( nullptr, "Error converting output string to UTF-8"); + + RETURN_FALSE; + } + + /* return the allocated string, not a duplicate */ + RETVAL_NEW_STR(u8_sub_str); +} +/* }}} */ + +/* {{{ strstr_common_handler */ +static void strstr_common_handler(INTERNAL_FUNCTION_PARAMETERS, int f_ignore_case) +{ + char *haystack, *needle, *locale = ""; + const char *found; + size_t haystack_len, needle_len, locale_len = 0; + int32_t ret_pos, uchar_pos; + bool part = false; + + ZEND_PARSE_PARAMETERS_START(2, 4) + Z_PARAM_STRING(haystack, haystack_len) + Z_PARAM_STRING(needle, needle_len) + Z_PARAM_OPTIONAL + Z_PARAM_BOOL(part) + Z_PARAM_PATH(locale, locale_len) + ZEND_PARSE_PARAMETERS_END(); + + if ( !f_ignore_case ) { -#define GRAPHEME_EXTRACT_TYPE_COUNT 0 -#define GRAPHEME_EXTRACT_TYPE_MAXBYTES 1 -#define GRAPHEME_EXTRACT_TYPE_MAXCHARS 2 -#define GRAPHEME_EXTRACT_TYPE_MIN GRAPHEME_EXTRACT_TYPE_COUNT -#define GRAPHEME_EXTRACT_TYPE_MAX GRAPHEME_EXTRACT_TYPE_MAXCHARS + /* ASCII optimization: quick check to see if the string might be there */ + found = php_memnstr(haystack, needle, needle_len, haystack + haystack_len); + + /* if it isn't there the we are done */ + if ( !found ) { + RETURN_FALSE; + } + + /* if it is there, and if the haystack is ascii, we are all done */ + if ( grapheme_ascii_check((unsigned char *)haystack, haystack_len) >= 0 ) { + size_t found_offset = found - haystack; + + if (part) { + RETURN_STRINGL(haystack, found_offset); + } else { + RETURN_STRINGL(found, haystack_len - found_offset); + } + } + + } + + /* need to work in utf16 */ + ret_pos = grapheme_strpos_utf16(haystack, haystack_len, needle, needle_len, 0, &uchar_pos, f_ignore_case, /* last */ 0, locale); + + if ( ret_pos < 0 ) { + RETURN_FALSE; + } + + /* uchar_pos is the 'nth' Unicode character position of the needle */ + + ret_pos = 0; + U8_FWD_N(haystack, ret_pos, haystack_len, uchar_pos); + + if (part) { + RETURN_STRINGL(haystack, ret_pos); + } else { + RETURN_STRINGL(haystack + ret_pos, haystack_len - ret_pos); + } + +} +/* }}} */ + +/* {{{ Finds first occurrence of a string within another */ +U_CFUNC PHP_FUNCTION(grapheme_strstr) +{ + strstr_common_handler(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0 /* f_ignore_case */); +} +/* }}} */ + +/* {{{ Finds first occurrence of a string within another */ +U_CFUNC PHP_FUNCTION(grapheme_stristr) +{ + strstr_common_handler(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1 /* f_ignore_case */); +} +/* }}} */ + +/* {{{ grapheme_extract_charcount_iter - grapheme iterator for grapheme_extract MAXCHARS */ +static inline int32_t +grapheme_extract_charcount_iter(UBreakIterator *bi, int32_t csize, unsigned char *pstr, int32_t str_len) +{ + int pos = 0; + int ret_pos = 0; + int break_pos, prev_break_pos; + int count = 0; + + while ( 1 ) { + pos = ubrk_next(bi); + + if ( UBRK_DONE == pos ) { + break; + } + + for ( break_pos = ret_pos; break_pos < pos; ) { + count++; + prev_break_pos = break_pos; + U8_FWD_1(pstr, break_pos, str_len); + + if ( prev_break_pos == break_pos ) { + /* something wrong - malformed utf8? */ + csize = 0; + break; + } + } + + /* if we are beyond our limit, then the loop is done */ + if ( count > csize ) { + break; + } + + ret_pos = break_pos; + } + + return ret_pos; +} +/* }}} */ + +/* {{{ grapheme_extract_bytecount_iter - grapheme iterator for grapheme_extract MAXBYTES */ +static inline int32_t +grapheme_extract_bytecount_iter(UBreakIterator *bi, int32_t bsize, unsigned char *pstr, int32_t str_len) +{ + int pos = 0; + int ret_pos = 0; + + while ( 1 ) { + pos = ubrk_next(bi); + + if ( UBRK_DONE == pos ) { + break; + } + + if ( pos > bsize ) { + break; + } + + ret_pos = pos; + } + + return ret_pos; +} +/* }}} */ + +/* {{{ grapheme_extract_count_iter - grapheme iterator for grapheme_extract COUNT */ +static inline int32_t +grapheme_extract_count_iter(UBreakIterator *bi, int32_t size, unsigned char *pstr, int32_t str_len) +{ + int next_pos = 0; + int ret_pos = 0; + + while ( size ) { + next_pos = ubrk_next(bi); + + if ( UBRK_DONE == next_pos ) { + break; + } + ret_pos = next_pos; + size--; + } + + return ret_pos; +} +/* }}} */ + +/* {{{ grapheme extract iter function pointer array */ +typedef int32_t (*grapheme_extract_iter)(UBreakIterator * /*bi*/, int32_t /*size*/, unsigned char * /*pstr*/, int32_t /*str_len*/); + +static const grapheme_extract_iter grapheme_extract_iters[] = { + &grapheme_extract_count_iter, + &grapheme_extract_bytecount_iter, + &grapheme_extract_charcount_iter, +}; +/* }}} */ + +/* {{{ Function to extract a sequence of default grapheme clusters */ +U_CFUNC PHP_FUNCTION(grapheme_extract) +{ + char *str, *pstr; + UText ut = UTEXT_INITIALIZER; + size_t str_len; + zend_long size; /* maximum number of grapheme clusters, bytes, or characters (based on extract_type) to return */ + zend_long lstart = 0; /* starting position in str in bytes */ + int32_t start = 0; + zend_long extract_type = GRAPHEME_EXTRACT_TYPE_COUNT; + UErrorCode status; + UBreakIterator* bi = nullptr; + int ret_pos; + zval *next = nullptr; /* return offset of next part of the string */ + + ZEND_PARSE_PARAMETERS_START(2, 5) + Z_PARAM_STRING(str, str_len) + Z_PARAM_LONG(size) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(extract_type) + Z_PARAM_LONG(lstart) + Z_PARAM_ZVAL(next) + ZEND_PARSE_PARAMETERS_END(); + + if (lstart < 0) { + lstart += str_len; + } + + if ( nullptr != next ) { + ZEND_ASSERT(Z_ISREF_P(next)); + ZEND_TRY_ASSIGN_REF_LONG(next, lstart); + if (UNEXPECTED(EG(exception))) { + RETURN_THROWS(); + } + } + + if ( extract_type < GRAPHEME_EXTRACT_TYPE_MIN || extract_type > GRAPHEME_EXTRACT_TYPE_MAX ) { + zend_argument_value_error(3, "must be one of GRAPHEME_EXTR_COUNT, GRAPHEME_EXTR_MAXBYTES, or GRAPHEME_EXTR_MAXCHARS"); + RETURN_THROWS(); + } + + if ( lstart > INT32_MAX || lstart < 0 || (size_t)lstart >= str_len ) { + intl_error_set( nullptr, U_ILLEGAL_ARGUMENT_ERROR, "start not contained in string"); + RETURN_FALSE; + } + + if (size < 0) { + zend_argument_value_error(2, "must be greater than or equal to 0"); + RETURN_THROWS(); + } + + if (size > INT32_MAX) { + zend_argument_value_error(2, "is too large"); + RETURN_THROWS(); + } + + if (size == 0) { + RETURN_EMPTY_STRING(); + } + + /* we checked that it will fit: */ + start = (int32_t) lstart; + + pstr = str + start; + + /* just in case pstr points in the middle of a character, move forward to the start of the next char */ + if ( !U8_IS_SINGLE(*pstr) && !U8_IS_LEAD(*pstr) ) { + char *str_end = str + str_len; + + while ( !U8_IS_SINGLE(*pstr) && !U8_IS_LEAD(*pstr) ) { + pstr++; + start++; + if ( pstr >= str_end ) { + intl_error_set( nullptr, U_ILLEGAL_ARGUMENT_ERROR, + "grapheme_extract: invalid input string"); + + RETURN_FALSE; + } + } + } + + str_len -= (pstr - str); + + /* if the string is all ASCII up to size+1 - or str_len whichever is first - then we are done. + (size + 1 because the size-th character might be the beginning of a grapheme cluster) + */ + + if ( -1 != grapheme_ascii_check((unsigned char *)pstr, MIN(size + 1, str_len)) ) { + size_t nsize = MIN(size, str_len); + if ( nullptr != next ) { + ZEND_TRY_ASSIGN_REF_LONG(next, start + nsize); + } + RETURN_STRINGL(pstr, nsize); + } + + status = U_ZERO_ERROR; + utext_openUTF8(&ut, pstr, str_len, &status); + + if ( U_FAILURE( status ) ) { + /* Set global error code. */ + intl_error_set_code( nullptr, status ); + + /* Set error messages. */ + intl_error_set_custom_msg( nullptr, "Error opening UTF-8 text"); + + RETURN_FALSE; + } + + bi = nullptr; + status = U_ZERO_ERROR; + bi = grapheme_get_break_iterator(&status); + + ubrk_setUText(bi, &ut, &status); + /* if the caller put us in the middle of a grapheme, we can't detect it in all cases since we + can't back up. So, we will not do anything. */ + + /* now we need to find the end of the chunk the user wants us to return */ + /* it's ok to convert str_len to in32_t since if it were too big intl_convert_utf8_to_utf16 above would fail */ + ret_pos = (*grapheme_extract_iters[extract_type])(bi, size, (unsigned char *)pstr, (int32_t)str_len); + + utext_close(&ut); + ubrk_close(bi); + + if ( nullptr != next ) { + ZEND_TRY_ASSIGN_REF_LONG(next, start + ret_pos); + } + + RETURN_STRINGL(((char *)pstr), ret_pos); +} + +U_CFUNC PHP_FUNCTION(grapheme_str_split) +{ + char *pstr, *end; + zend_string *str; + zend_long split_len = 1; + + UErrorCode ustatus = U_ZERO_ERROR; + int32_t pos, current, i, end_len = 0; + UBreakIterator* bi; + UText *ut = nullptr; + + ZEND_PARSE_PARAMETERS_START(1, 2) + Z_PARAM_STR(str) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(split_len) + ZEND_PARSE_PARAMETERS_END(); + + if (split_len <= 0 || split_len > UINT_MAX / 4) { + zend_argument_value_error(2, "must be greater than 0 and less than or equal to %d", UINT_MAX / 4); + RETURN_THROWS(); + } + + if (ZSTR_LEN(str) == 0) { + RETURN_EMPTY_ARRAY(); + } + + pstr = ZSTR_VAL(str); + ut = utext_openUTF8(ut, pstr, ZSTR_LEN(str), &ustatus); + + if ( U_FAILURE( ustatus ) ) { + /* Set global error code. */ + intl_error_set_code( nullptr, ustatus ); + + /* Set error messages. */ + intl_error_set_custom_msg( nullptr, "Error opening UTF-8 text"); + + RETURN_FALSE; + } + + bi = nullptr; + ustatus = U_ZERO_ERROR; + bi = grapheme_get_break_iterator(&ustatus); + + if( U_FAILURE(ustatus) ) { + RETURN_FALSE; + } + + ubrk_setUText(bi, ut, &ustatus); + + pos = 0; + array_init(return_value); + + for (end = pstr, i = 0, current = 0; pos != UBRK_DONE;) { + end_len = pos - current; + pos = ubrk_next(bi); + + if (i == split_len - 1) { + if ( pos != UBRK_DONE ) { + add_next_index_stringl(return_value, pstr, pos - current); + end = pstr + pos - current; + i = 0; + } + pstr += pos - current; + current = pos; + } else { + i += 1; + } + } + + if (i != 0 && end_len != 0) { + add_next_index_stringl(return_value, end, end_len); + } + + utext_close(ut); + ubrk_close(bi); +} + +U_CFUNC PHP_FUNCTION(grapheme_levenshtein) +{ + zend_string *string1, *string2; + zend_long cost_ins = 1; + zend_long cost_rep = 1; + zend_long cost_del = 1; + char *locale = ""; + size_t locale_len = 0; + + ZEND_PARSE_PARAMETERS_START(2, 6) + Z_PARAM_STR(string1) + Z_PARAM_STR(string2) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(cost_ins) + Z_PARAM_LONG(cost_rep) + Z_PARAM_LONG(cost_del) + Z_PARAM_PATH(locale, locale_len) + ZEND_PARSE_PARAMETERS_END(); + + if (cost_ins <= 0 || cost_ins > UINT_MAX / 4) { + zend_argument_value_error(3, "must be greater than 0 and less than or equal to %d", UINT_MAX / 4); + RETURN_THROWS(); + } + + if (cost_rep <= 0 || cost_rep > UINT_MAX / 4) { + zend_argument_value_error(4, "must be greater than 0 and less than or equal to %d", UINT_MAX / 4); + RETURN_THROWS(); + } + + if (cost_del <= 0 || cost_del > UINT_MAX / 4) { + zend_argument_value_error(5, "must be greater than 0 and less than or equal to %d", UINT_MAX / 4); + RETURN_THROWS(); + } + + zend_long c0, c1, c2; + zend_long retval; + size_t i2; + char *pstr1, *pstr2; + + UChar *ustring1 = nullptr; + UChar *ustring2 = nullptr; + + int32_t ustring1_len = 0; + int32_t ustring2_len = 0; + int32_t current1 = 0; + int32_t current2 = 0; + int32_t pos1 = 0; + int32_t pos2 = 0; + + UCollator *collator = nullptr; + + UErrorCode ustatus = U_ZERO_ERROR; + + /* When all costs are equal, levenshtein fulfills the requirements of a metric, which means + * that the distance is symmetric. If string1 is shorter than string2 we can save memory (and CPU time) + * by having shorter rows (p1 & p2). */ + if (ZSTR_LEN(string1) < ZSTR_LEN(string2) && cost_ins == cost_rep && cost_rep == cost_del) { + zend_string *tmp = string1; + string1 = string2; + string2 = tmp; + } + + pstr1 = ZSTR_VAL(string1); + pstr2 = ZSTR_VAL(string2); + + intl_convert_utf8_to_utf16(&ustring1, &ustring1_len, pstr1, ZSTR_LEN(string1), &ustatus); + + if (U_FAILURE(ustatus)) { + intl_error_set_code(NULL, ustatus); + + intl_error_set_custom_msg(NULL, "Error converting input string to UTF-16"); + RETVAL_FALSE; + goto out_ustring1; + } + + intl_convert_utf8_to_utf16(&ustring2, &ustring2_len, pstr2, ZSTR_LEN(string2), &ustatus); + + if (U_FAILURE(ustatus)) { + intl_error_set_code(NULL, ustatus); + + intl_error_set_custom_msg(NULL, "Error converting input string to UTF-16"); + RETVAL_FALSE; + goto out_ustring2; + } + + UBreakIterator *bi1, *bi2; + + int32_t strlen_1, strlen_2; + strlen_1 = grapheme_split_string(ustring1, ustring1_len, nullptr, 0); + strlen_2 = grapheme_split_string(ustring2, ustring2_len, nullptr, 0); + if (UNEXPECTED(strlen_1 < 0 || strlen_2 < 0)) { + RETVAL_FALSE; + goto out_ustring2; + } + + if (strlen_1 == 0) { + RETVAL_LONG(strlen_2 * cost_ins); + goto out_ustring2; + } + if (strlen_2 == 0) { + RETVAL_LONG(strlen_1 * cost_del); + goto out_ustring2; + } + + bi1 = grapheme_get_break_iterator(&ustatus); + if (U_FAILURE(ustatus)) { + intl_error_set_code(NULL, ustatus); + intl_error_set_custom_msg(NULL, "Error on grapheme_get_break_iterator for argument #1 ($string1)"); + RETVAL_FALSE; + goto out_bi1; + } + + bi2 = grapheme_get_break_iterator(&ustatus); + if (U_FAILURE(ustatus)) { + intl_error_set_code(NULL, ustatus); + intl_error_set_custom_msg(NULL, "Error on grapheme_get_break_iterator for argument #2 ($string2)"); + RETVAL_FALSE; + goto out_bi2; + } + + ubrk_setText(bi1, ustring1, ustring1_len, &ustatus); + if (U_FAILURE(ustatus)) { + intl_error_set_code(NULL, ustatus); + + intl_error_set_custom_msg(NULL, "Error on ubrk_setText for argument #1 ($string1)"); + RETVAL_FALSE; + goto out_bi2; + } + + ubrk_setText(bi2, ustring2, ustring2_len, &ustatus); + if (U_FAILURE(ustatus)) { + intl_error_set_code(NULL, ustatus); + + intl_error_set_custom_msg(NULL, "Error on ubrk_setText for argument #2 ($string2)"); + RETVAL_FALSE; + goto out_bi2; + } + collator = ucol_open(locale, &ustatus); + if (U_FAILURE(ustatus)) { + intl_error_set_code(NULL, ustatus); + + intl_error_set_custom_msg(NULL, "Error on ucol_open"); + RETVAL_FALSE; + goto out_collator; + } + + zend_long *p1, *p2, *tmp; + p1 = reinterpret_cast(safe_emalloc((size_t) strlen_2 + 1, sizeof(zend_long), 0)); + p2 = reinterpret_cast(safe_emalloc((size_t) strlen_2 + 1, sizeof(zend_long), 0)); + + for (i2 = 0; i2 <= strlen_2; i2++) { + p1[i2] = i2 * cost_ins; + } + + while (true) { + current1 = ubrk_current(bi1); + pos1 = ubrk_next(bi1); + if (pos1 == UBRK_DONE) { + break; + } + p2[0] = p1[0] + cost_del; + for (i2 = 0, pos2 = 0; pos2 != UBRK_DONE; i2++) { + current2 = ubrk_current(bi2); + pos2 = ubrk_next(bi2); + if (pos2 == UBRK_DONE) { + break; + } + if (ucol_strcoll(collator, ustring1 + current1, pos1 - current1, ustring2 + current2, pos2 - current2) == UCOL_EQUAL) { + c0 = p1[i2]; + } else { + c0 = p1[i2] + cost_rep; + } + c1 = p1[i2 + 1] + cost_del; + if (c1 < c0) { + c0 = c1; + } + c2 = p2[i2] + cost_ins; + if (c2 < c0) { + c0 = c2; + } + p2[i2 + 1] = c0; + } + ubrk_first(bi2); + tmp = p1; + p1 = p2; + p2 = tmp; + } + + retval = p1[strlen_2]; + RETVAL_LONG(retval); + + efree(p2); + efree(p1); + +out_collator: + ucol_close(collator); +out_bi2: + ubrk_close(bi2); +out_bi1: + ubrk_close(bi1); +out_ustring2: + efree(ustring2); +out_ustring1: + efree(ustring1); +} + +U_CFUNC PHP_FUNCTION(grapheme_strrev) +{ + zend_string *string; + UText *ut = nullptr; + UErrorCode ustatus = U_ZERO_ERROR; + UBreakIterator *bi; + char *pstr, *end, *p; + zend_string *ret; + int32_t pos = 0, current = 0, end_len = 0; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_STR(string) + ZEND_PARSE_PARAMETERS_END(); + + if (ZSTR_LEN(string) == 0) { + RETURN_EMPTY_STRING(); + } + + pstr = ZSTR_VAL(string); + ut = utext_openUTF8(ut, pstr, ZSTR_LEN(string), &ustatus); + + if (U_FAILURE(ustatus)) { + intl_error_set_code(nullptr, ustatus); + intl_error_set_custom_msg(nullptr, "Error opening UTF-8 text"); + + RETVAL_FALSE; + goto close; + } + + bi = nullptr; + ustatus = U_ZERO_ERROR; + + bi = grapheme_get_break_iterator(&ustatus); + ret = zend_string_alloc(ZSTR_LEN(string), 0); + p = ZSTR_VAL(ret); + + ubrk_setUText(bi, ut, &ustatus); + pos = ubrk_last(bi); + if (pos == UBRK_DONE) { + goto ubrk_end; + } + + current = ZSTR_LEN(string); + for (end = pstr; pos != UBRK_DONE; ) { + pos = ubrk_previous(bi); + end_len = current - pos; + for (int32_t j = 0; j < end_len; j++) { + *p++ = *(pstr + pos + j); + } + current = pos; + } +ubrk_end: + RETVAL_NEW_STR(ret); + ubrk_close(bi); +close: + utext_close(ut); +} -#endif // GRAPHEME_GRAPHEME_H +/* }}} */ diff --git a/ext/intl/grapheme/grapheme_arginfo.h b/ext/intl/grapheme/grapheme_arginfo.h new file mode 100644 index 000000000000..35ef7e67bc8e --- /dev/null +++ b/ext/intl/grapheme/grapheme_arginfo.h @@ -0,0 +1,6 @@ +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_grapheme_mask, 0, 1, MAY_BE_STRING|MAY_BE_FALSE) + ZEND_ARG_TYPE_INFO(0, string, IS_STRING, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, mask_char, IS_STRING, 0, "\"*\"") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, offset, IS_LONG, 0, "0") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, length, IS_LONG, 1, "null") +ZEND_END_ARG_INFO() diff --git a/ext/intl/grapheme/grapheme_util.h b/ext/intl/grapheme/grapheme_util.h index 0ddfcd3da88f..200731dc3b98 100644 --- a/ext/intl/grapheme/grapheme_util.h +++ b/ext/intl/grapheme/grapheme_util.h @@ -1,17 +1,3 @@ -/* - +----------------------------------------------------------------------+ - | Copyright © The PHP Group and Contributors. | - +----------------------------------------------------------------------+ - | This source file is subject to the Modified BSD License that is | - | bundled with this package in the file LICENSE, and is available | - | through the World Wide Web at . | - | | - | SPDX-License-Identifier: BSD-3-Clause | - +----------------------------------------------------------------------+ - | Authors: Ed Batutis | - +----------------------------------------------------------------------+ - */ - #ifndef GRAPHEME_GRAPHEME_UTIL_H #define GRAPHEME_GRAPHEME_UTIL_H @@ -33,11 +19,35 @@ int32_t grapheme_strpos_utf16(char *haystack, size_t haystack_len, char *needle, int32_t grapheme_split_string(const UChar *text, int32_t text_length, int boundary_array[], int boundary_array_len ); -int32_t grapheme_count_graphemes(UBreakIterator *bi, UChar *string, int32_t string_len); +int32_t grapheme_count_graphemes(UText *ut); /* تغییر: UBreakIterator* → UText* */ int32_t grapheme_get_haystack_offset(UBreakIterator* bi, int32_t offset); -UBreakIterator* grapheme_get_break_iterator(UErrorCode *status ); +/* توابع UTF-8 که در grapheme_string.c تعریف شدن */ +int32_t grapheme_strpos_utf8(const char *haystack, size_t haystack_len, + const char *needle, size_t needle_len, + int32_t offset, int32_t *puchar_pos); + +int32_t grapheme_stripos_utf8(const char *haystack, size_t haystack_len, + const char *needle, size_t needle_len, + int32_t offset, int32_t *puchar_pos); + +int32_t grapheme_strrpos_utf8(const char *haystack, size_t haystack_len, + const char *needle, size_t needle_len, + int32_t offset, int32_t *puchar_pos); + +int32_t grapheme_strripos_utf8(const char *haystack, size_t haystack_len, + const char *needle, size_t needle_len, + int32_t offset, int32_t *puchar_pos); + +int32_t grapheme_extract_utf8(const char *str, size_t str_len, + int32_t size, int32_t extract_type, + int32_t start, int32_t *next_pos); + +UText *grapheme_open_utext(UText *ut, const char *str, size_t str_len); + +#define GRAPHEME_STRPOS_UTF8_MAXLEN INT32_MAX + #ifdef __cplusplus } #endif diff --git a/ext/intl/tests/grapheme_mask.phpt b/ext/intl/tests/grapheme_mask.phpt new file mode 100644 index 000000000000..d57f7f224a16 --- /dev/null +++ b/ext/intl/tests/grapheme_mask.phpt @@ -0,0 +1,43 @@ +--TEST-- +grapheme_mask() - Basic functionality +--SKIPIF-- + +--FILE-- +getMessage() . "\n"; +} + +// Test with emoji sequence (should work - single grapheme cluster) +var_dump(grapheme_mask("Hello", "👨‍👩‍👧‍👦")); + +// Test with combining characters (should work - single grapheme cluster) +var_dump(grapheme_mask("Hello", "c\u0301")); // c with acute accent + +// Test with ZWJ sequence (should work - single grapheme cluster) +var_dump(grapheme_mask("Hello", "👨‍💻")); +?> +--EXPECT-- +string(11) "XXXXX XXXXX" +string(11) "HelXX XXXXX" +string(11) "HelXXo XXXXX" +string(11) "Hello WXXld" +string(11) "Hello World" +string(0) "" +string(11) "👍👍👍👍👍 👍👍👍👍👍" +grapheme_mask(): Argument #2 ($mask_char) must be exactly one grapheme cluster +string(5) "👨‍👩‍👧‍👦👨‍👩‍👧‍👦👨‍👩‍👧‍👦👨‍👩‍👧‍👦👨‍👩‍👧‍👦" +string(5) "ććććć" +string(5) "👨‍💻👨‍💻👨‍💻👨‍💻👨‍💻"