Skip to content

Commit 066d5e4

Browse files
LamentXU123devnexen
andcommitted
ext/intl: Add Transliterator::toRules()
Expose transliterator rules through Transliterator::toRules() and transliterator_to_rules(), with optional escaping of unprintable characters. Use ICU's C++ API to export the rules into a UnicodeString. Co-authored-by: David Carlier <devnexen@gmail.com>
1 parent 1cc5591 commit 066d5e4

10 files changed

Lines changed: 143 additions & 2 deletions

‎NEWS‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ PHP NEWS
1818
throwing a ValueError. (Marc Bennewitz)
1919

2020
- Intl:
21+
. Added Transliterator::toRules() and transliterator_to_rules().
22+
(Weilin Du, David Carlier)
2123
. Fixed Collator attribute and strength methods not rejecting an
2224
unconstructed Collator. (Ilia Alshanetsky)
2325
. Added optional $availableLocales argument to Locale::acceptFromHttp()

‎UPGRADING‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,11 @@ PHP 8.7 UPGRADE NOTES
8585
6. New Functions
8686
========================================
8787

88+
- Intl:
89+
. Added Transliterator::toRules() and transliterator_to_rules() to export
90+
rules that can be used to recreate a transliterator. The optional
91+
$escapeUnprintable argument escapes unprintable characters when true.
92+
8893
========================================
8994
7. New Classes and Interfaces
9095
========================================

‎ext/intl/php_intl.stub.php‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -640,6 +640,8 @@ function transliterator_list_ids(): array|false {}
640640

641641
function transliterator_create_inverse(Transliterator $transliterator): ?Transliterator {}
642642

643+
function transliterator_to_rules(Transliterator $transliterator, bool $escapeUnprintable = false): string|false {}
644+
643645
function transliterator_transliterate(Transliterator|string $transliterator, string $string, int $start = 0, int $end = -1): string|false {}
644646

645647
function transliterator_get_error_code(Transliterator $transliterator): int {}

‎ext/intl/php_intl_arginfo.h‎

Lines changed: 8 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
--TEST--
2+
Transliterator::toRules() and transliterator_to_rules()
3+
--EXTENSIONS--
4+
intl
5+
--FILE--
6+
<?php
7+
$transliterator = Transliterator::createFromRules('a > b;');
8+
var_dump($transliterator->toRules());
9+
var_dump(transliterator_to_rules($transliterator));
10+
11+
foreach ([
12+
Transliterator::create('Any-Lower; Latin-ASCII'),
13+
Transliterator::createFromRules('a <> b;', Transliterator::REVERSE),
14+
Transliterator::createFromRules(''),
15+
Transliterator::createFromRules('a > ' . str_repeat('b', 4096) . ';'),
16+
] as $transliterator) {
17+
$copy = Transliterator::createFromRules($transliterator->toRules());
18+
var_dump($copy->transliterate('ab ÄÉ') === $transliterator->transliterate('ab ÄÉ'));
19+
}
20+
?>
21+
--EXPECT--
22+
string(6) "a > b;"
23+
string(6) "a > b;"
24+
bool(true)
25+
bool(true)
26+
bool(true)
27+
bool(true)
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
--TEST--
2+
Transliterator::toRules() validates objects and resets previous errors
3+
--EXTENSIONS--
4+
intl
5+
--INI--
6+
intl.error_level=0
7+
--FILE--
8+
<?php
9+
$transliterator = Transliterator::createFromRules('a > b;');
10+
var_dump($transliterator->transliterate("\xff"));
11+
var_dump($transliterator->toRules());
12+
var_dump($transliterator->getErrorCode(), intl_get_error_code());
13+
14+
class UnconstructedTransliterator extends Transliterator {}
15+
$unconstructed = (new ReflectionClass(UnconstructedTransliterator::class))->newInstanceWithoutConstructor();
16+
try {
17+
$unconstructed->toRules();
18+
} catch (Error $e) {
19+
echo $e->getMessage(), "\n";
20+
}
21+
?>
22+
--EXPECT--
23+
bool(false)
24+
string(6) "a > b;"
25+
int(0)
26+
int(0)
27+
Found unconstructed transliterator
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
--TEST--
2+
Transliterator::toRules() escapes Unicode characters and preserves rules
3+
--EXTENSIONS--
4+
intl
5+
--FILE--
6+
<?php
7+
$transliterator = Transliterator::createFromRules('α > a; \U0001F600 > x;');
8+
$rules = $transliterator->toRules();
9+
var_dump(str_contains($rules, 'α'));
10+
var_dump(str_contains($rules, '😀'));
11+
12+
$escaped = transliterator_to_rules($transliterator, escapeUnprintable: true);
13+
var_dump(preg_match('/[^\x00-\x7f]/', $escaped));
14+
15+
foreach ([$rules, $escaped] as $exported) {
16+
$copy = Transliterator::createFromRules($exported);
17+
var_dump($copy->transliterate('α😀'));
18+
}
19+
20+
// Preserve embedded NULs in the output; escape them to make the rules parseable.
21+
$transliterator = Transliterator::createFromRules('\u0000 > z;');
22+
var_dump(str_contains($transliterator->toRules(), "\0"));
23+
$escaped = $transliterator->toRules(true);
24+
var_dump(Transliterator::createFromRules($escaped)->transliterate("\0"));
25+
?>
26+
--EXPECT--
27+
bool(true)
28+
bool(true)
29+
int(0)
30+
string(2) "ax"
31+
string(2) "ax"
32+
bool(true)
33+
string(1) "z"

‎ext/intl/transliterator/transliterator.stub.php‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ public static function createFromRules(string $rules, int $direction = Translite
3232
*/
3333
public function createInverse(): ?Transliterator {}
3434

35+
/** @alias transliterator_to_rules */
36+
public function toRules(bool $escapeUnprintable = false): string|false {}
37+
3538
/**
3639
* @return array<int, string>|false
3740
* @tentative-return-type

‎ext/intl/transliterator/transliterator_arginfo.h‎

Lines changed: 7 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎ext/intl/transliterator/transliterator_methods.cpp‎

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
#endif
2323

2424
#include <unicode/uenum.h>
25+
#include <unicode/translit.h>
26+
#include "../intl_convertcpp.h"
2527

2628
extern "C" {
2729
#include "php_intl.h"
@@ -222,6 +224,33 @@ U_CFUNC PHP_FUNCTION( transliterator_create_inverse )
222224
}
223225
/* }}} */
224226

227+
/* {{{ Export the rules used to recreate a transliterator. */
228+
U_CFUNC PHP_FUNCTION( transliterator_to_rules )
229+
{
230+
bool escape_unprintable = false;
231+
TRANSLITERATOR_METHOD_INIT_VARS;
232+
233+
if (zend_parse_method_parameters(ZEND_NUM_ARGS(), getThis(), "O|b",
234+
&object, Transliterator_ce_ptr, &escape_unprintable) == FAILURE) {
235+
RETURN_THROWS();
236+
}
237+
238+
TRANSLITERATOR_METHOD_FETCH_OBJECT;
239+
240+
UnicodeString rules;
241+
reinterpret_cast<const icu::Transliterator *>(to->utrans)->toRules(rules, escape_unprintable);
242+
243+
zend_string *u8str = intl_charFromString(rules, TRANSLITERATOR_ERROR_CODE_P(to));
244+
if (!u8str) {
245+
if (U_SUCCESS(TRANSLITERATOR_ERROR_CODE(to))) {
246+
TRANSLITERATOR_ERROR_CODE(to) = U_MEMORY_ALLOCATION_ERROR;
247+
}
248+
INTL_METHOD_CHECK_STATUS(to, "Failed to export transliterator rules");
249+
}
250+
RETURN_STR(u8str);
251+
}
252+
/* }}} */
253+
225254
/* {{{ Return an array with the registered transliterator IDs. */
226255
PHP_INTL_FUNCTION_WITH_ERROR_RESET(transliterator_list_ids)
227256
{

0 commit comments

Comments
 (0)