diff --git a/NEWS b/NEWS index 67c44a4420e9..95725d52b77e 100644 --- a/NEWS +++ b/NEWS @@ -18,6 +18,8 @@ PHP NEWS throwing a ValueError. (Marc Bennewitz) - Intl: + . Added Transliterator::toRules() and transliterator_to_rules(). + (Weilin Du, David Carlier) . Fixed Collator attribute and strength methods not rejecting an unconstructed Collator. (Ilia Alshanetsky) . Added optional $availableLocales argument to Locale::acceptFromHttp() diff --git a/UPGRADING b/UPGRADING index fd3efda9df7a..f1d32270f1fa 100644 --- a/UPGRADING +++ b/UPGRADING @@ -85,6 +85,11 @@ PHP 8.7 UPGRADE NOTES 6. New Functions ======================================== +- Intl: + . Added Transliterator::toRules() and transliterator_to_rules() to export + rules that can be used to recreate a transliterator. The optional + $escapeUnprintable argument escapes unprintable characters when true. + ======================================== 7. New Classes and Interfaces ======================================== diff --git a/ext/intl/php_intl.stub.php b/ext/intl/php_intl.stub.php index a30c2d987608..a6a519e50f65 100644 --- a/ext/intl/php_intl.stub.php +++ b/ext/intl/php_intl.stub.php @@ -640,6 +640,8 @@ function transliterator_list_ids(): array|false {} function transliterator_create_inverse(Transliterator $transliterator): ?Transliterator {} +function transliterator_to_rules(Transliterator $transliterator, bool $escapeUnprintable = false): string|false {} + function transliterator_transliterate(Transliterator|string $transliterator, string $string, int $start = 0, int $end = -1): string|false {} function transliterator_get_error_code(Transliterator $transliterator): int {} diff --git a/ext/intl/php_intl_arginfo.h b/ext/intl/php_intl_arginfo.h index 9478a818d383..a1b95671d275 100644 --- a/ext/intl/php_intl_arginfo.h +++ b/ext/intl/php_intl_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit php_intl.stub.php instead. - * Stub hash: bb8c86f1c7f0dcc264e5968fe9548f20ac20908b */ + * Stub hash: c54e9724edabab5a6531e535f2226f512a904c8e */ #include "zend_attributes.h" #include "zend_constants.h" @@ -814,6 +814,11 @@ ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_transliterator_create_inverse, 0, ZEND_ARG_OBJ_INFO(0, transliterator, Transliterator, 0) ZEND_END_ARG_INFO() +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_transliterator_to_rules, 0, 1, MAY_BE_STRING|MAY_BE_FALSE) + ZEND_ARG_OBJ_INFO(0, transliterator, Transliterator, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, escapeUnprintable, _IS_BOOL, 0, "false") +ZEND_END_ARG_INFO() + ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_transliterator_transliterate, 0, 2, MAY_BE_STRING|MAY_BE_FALSE) ZEND_ARG_OBJ_TYPE_MASK(0, transliterator, Transliterator, MAY_BE_STRING, NULL) ZEND_ARG_TYPE_INFO(0, string, IS_STRING, 0) @@ -1018,6 +1023,7 @@ ZEND_FUNCTION(transliterator_create); ZEND_FUNCTION(transliterator_create_from_rules); ZEND_FUNCTION(transliterator_list_ids); ZEND_FUNCTION(transliterator_create_inverse); +ZEND_FUNCTION(transliterator_to_rules); ZEND_FUNCTION(transliterator_transliterate); ZEND_FUNCTION(transliterator_get_error_code); ZEND_FUNCTION(transliterator_get_error_message); @@ -1212,6 +1218,7 @@ static const zend_function_entry ext_functions[] = { ZEND_FE(transliterator_create_from_rules, arginfo_transliterator_create_from_rules) ZEND_FE(transliterator_list_ids, arginfo_transliterator_list_ids) ZEND_FE(transliterator_create_inverse, arginfo_transliterator_create_inverse) + ZEND_FE(transliterator_to_rules, arginfo_transliterator_to_rules) ZEND_FE(transliterator_transliterate, arginfo_transliterator_transliterate) ZEND_FE(transliterator_get_error_code, arginfo_transliterator_get_error_code) ZEND_FE(transliterator_get_error_message, arginfo_transliterator_get_error_message) diff --git a/ext/intl/tests/transliterator_to_rules_basic.phpt b/ext/intl/tests/transliterator_to_rules_basic.phpt new file mode 100644 index 000000000000..0a3be7fac96d --- /dev/null +++ b/ext/intl/tests/transliterator_to_rules_basic.phpt @@ -0,0 +1,27 @@ +--TEST-- +Transliterator::toRules() and transliterator_to_rules() +--EXTENSIONS-- +intl +--FILE-- + b;'); +var_dump($transliterator->toRules()); +var_dump(transliterator_to_rules($transliterator)); + +foreach ([ + Transliterator::create('Any-Lower; Latin-ASCII'), + Transliterator::createFromRules('a <> b;', Transliterator::REVERSE), + Transliterator::createFromRules(''), + Transliterator::createFromRules('a > ' . str_repeat('b', 4096) . ';'), +] as $transliterator) { + $copy = Transliterator::createFromRules($transliterator->toRules()); + var_dump($copy->transliterate('ab ÄÉ') === $transliterator->transliterate('ab ÄÉ')); +} +?> +--EXPECT-- +string(6) "a > b;" +string(6) "a > b;" +bool(true) +bool(true) +bool(true) +bool(true) diff --git a/ext/intl/tests/transliterator_to_rules_error.phpt b/ext/intl/tests/transliterator_to_rules_error.phpt new file mode 100644 index 000000000000..e1381bcd7231 --- /dev/null +++ b/ext/intl/tests/transliterator_to_rules_error.phpt @@ -0,0 +1,27 @@ +--TEST-- +Transliterator::toRules() validates objects and resets previous errors +--EXTENSIONS-- +intl +--INI-- +intl.error_level=0 +--FILE-- + b;'); +var_dump($transliterator->transliterate("\xff")); +var_dump($transliterator->toRules()); +var_dump($transliterator->getErrorCode(), intl_get_error_code()); + +class UnconstructedTransliterator extends Transliterator {} +$unconstructed = (new ReflectionClass(UnconstructedTransliterator::class))->newInstanceWithoutConstructor(); +try { + $unconstructed->toRules(); +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} +?> +--EXPECT-- +bool(false) +string(6) "a > b;" +int(0) +int(0) +Found unconstructed transliterator diff --git a/ext/intl/tests/transliterator_to_rules_unicode.phpt b/ext/intl/tests/transliterator_to_rules_unicode.phpt new file mode 100644 index 000000000000..77d2e213aff0 --- /dev/null +++ b/ext/intl/tests/transliterator_to_rules_unicode.phpt @@ -0,0 +1,33 @@ +--TEST-- +Transliterator::toRules() escapes Unicode characters and preserves rules +--EXTENSIONS-- +intl +--FILE-- + a; \U0001F600 > x;'); +$rules = $transliterator->toRules(); +var_dump(str_contains($rules, 'α')); +var_dump(str_contains($rules, '😀')); + +$escaped = transliterator_to_rules($transliterator, escapeUnprintable: true); +var_dump(preg_match('/[^\x00-\x7f]/', $escaped)); + +foreach ([$rules, $escaped] as $exported) { + $copy = Transliterator::createFromRules($exported); + var_dump($copy->transliterate('α😀')); +} + +// Preserve embedded NULs in the output; escape them to make the rules parseable. +$transliterator = Transliterator::createFromRules('\u0000 > z;'); +var_dump(str_contains($transliterator->toRules(), "\0")); +$escaped = $transliterator->toRules(true); +var_dump(Transliterator::createFromRules($escaped)->transliterate("\0")); +?> +--EXPECT-- +bool(true) +bool(true) +int(0) +string(2) "ax" +string(2) "ax" +bool(true) +string(1) "z" diff --git a/ext/intl/transliterator/transliterator.stub.php b/ext/intl/transliterator/transliterator.stub.php index df65b8c7f0d4..198c6fea0c3e 100644 --- a/ext/intl/transliterator/transliterator.stub.php +++ b/ext/intl/transliterator/transliterator.stub.php @@ -32,6 +32,9 @@ public static function createFromRules(string $rules, int $direction = Translite */ public function createInverse(): ?Transliterator {} + /** @alias transliterator_to_rules */ + public function toRules(bool $escapeUnprintable = false): string|false {} + /** * @return array|false * @tentative-return-type diff --git a/ext/intl/transliterator/transliterator_arginfo.h b/ext/intl/transliterator/transliterator_arginfo.h index 2d9e80708d19..42257294c3a4 100644 --- a/ext/intl/transliterator/transliterator_arginfo.h +++ b/ext/intl/transliterator/transliterator_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit transliterator.stub.php instead. - * Stub hash: 300bcc64e5ddaf469bfe4a12e65a6677bf2aea88 */ + * Stub hash: 54eab5ebc2589b5f0a22998979c7409da1a9bfb1 */ ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Transliterator___construct, 0, 0, 0) ZEND_END_ARG_INFO() @@ -17,6 +17,10 @@ ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_OBJ_INFO_EX(arginfo_class_Transliterator_createInverse, 0, 0, Transliterator, 1) ZEND_END_ARG_INFO() +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_class_Transliterator_toRules, 0, 0, MAY_BE_STRING|MAY_BE_FALSE) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, escapeUnprintable, _IS_BOOL, 0, "false") +ZEND_END_ARG_INFO() + ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_MASK_EX(arginfo_class_Transliterator_listIDs, 0, 0, MAY_BE_ARRAY|MAY_BE_FALSE) ZEND_END_ARG_INFO() @@ -36,6 +40,7 @@ ZEND_METHOD(Transliterator, __construct); ZEND_FUNCTION(transliterator_create); ZEND_FUNCTION(transliterator_create_from_rules); ZEND_FUNCTION(transliterator_create_inverse); +ZEND_FUNCTION(transliterator_to_rules); ZEND_FUNCTION(transliterator_list_ids); ZEND_FUNCTION(transliterator_transliterate); ZEND_FUNCTION(transliterator_get_error_code); @@ -46,6 +51,7 @@ static const zend_function_entry class_Transliterator_methods[] = { ZEND_RAW_FENTRY("create", zif_transliterator_create, arginfo_class_Transliterator_create, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC, NULL, NULL) ZEND_RAW_FENTRY("createFromRules", zif_transliterator_create_from_rules, arginfo_class_Transliterator_createFromRules, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC, NULL, NULL) ZEND_RAW_FENTRY("createInverse", zif_transliterator_create_inverse, arginfo_class_Transliterator_createInverse, ZEND_ACC_PUBLIC, NULL, NULL) + ZEND_RAW_FENTRY("toRules", zif_transliterator_to_rules, arginfo_class_Transliterator_toRules, ZEND_ACC_PUBLIC, NULL, NULL) ZEND_RAW_FENTRY("listIDs", zif_transliterator_list_ids, arginfo_class_Transliterator_listIDs, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC, NULL, NULL) ZEND_RAW_FENTRY("transliterate", zif_transliterator_transliterate, arginfo_class_Transliterator_transliterate, ZEND_ACC_PUBLIC, NULL, NULL) ZEND_RAW_FENTRY("getErrorCode", zif_transliterator_get_error_code, arginfo_class_Transliterator_getErrorCode, ZEND_ACC_PUBLIC, NULL, NULL) diff --git a/ext/intl/transliterator/transliterator_methods.cpp b/ext/intl/transliterator/transliterator_methods.cpp index d84fdbbe36a5..db92be768241 100644 --- a/ext/intl/transliterator/transliterator_methods.cpp +++ b/ext/intl/transliterator/transliterator_methods.cpp @@ -22,6 +22,8 @@ #endif #include +#include +#include "../intl_convertcpp.h" extern "C" { #include "php_intl.h" @@ -222,6 +224,33 @@ U_CFUNC PHP_FUNCTION( transliterator_create_inverse ) } /* }}} */ +/* {{{ Export the rules used to recreate a transliterator. */ +U_CFUNC PHP_FUNCTION( transliterator_to_rules ) +{ + bool escape_unprintable = false; + TRANSLITERATOR_METHOD_INIT_VARS; + + if (zend_parse_method_parameters(ZEND_NUM_ARGS(), getThis(), "O|b", + &object, Transliterator_ce_ptr, &escape_unprintable) == FAILURE) { + RETURN_THROWS(); + } + + TRANSLITERATOR_METHOD_FETCH_OBJECT; + + UnicodeString rules; + reinterpret_cast(to->utrans)->toRules(rules, escape_unprintable); + + zend_string *u8str = intl_charFromString(rules, TRANSLITERATOR_ERROR_CODE_P(to)); + if (!u8str) { + if (U_SUCCESS(TRANSLITERATOR_ERROR_CODE(to))) { + TRANSLITERATOR_ERROR_CODE(to) = U_MEMORY_ALLOCATION_ERROR; + } + INTL_METHOD_CHECK_STATUS(to, "Failed to export transliterator rules"); + } + RETURN_STR(u8str); +} +/* }}} */ + /* {{{ Return an array with the registered transliterator IDs. */ PHP_INTL_FUNCTION_WITH_ERROR_RESET(transliterator_list_ids) {