diff --git a/src/common/Str.php b/src/common/Str.php index c454a279..19ebaec3 100644 --- a/src/common/Str.php +++ b/src/common/Str.php @@ -21,12 +21,12 @@ use Error; use Nette\Utils\Strings; use UConverter; -use function ord; use const MB_CASE_TITLE; use function array_keys; use function array_pop; use function array_shift; use function array_values; +use function chr; use function class_exists; use function count; use function error_clear_last; @@ -42,6 +42,7 @@ use function mb_strtoupper; use function mb_substr; use function min; +use function ord; use function range; use function str_replace; use function strcasecmp; @@ -76,7 +77,25 @@ public static function fixEncoding(string $string): string public static function chr(int $code): string { - return Strings::chr($code); + if ($code < 0 || ($code >= 0xD800 && $code <= 0xDFFF) || $code > 0x10FFFF) { + throw new InvalidValueException($code, 'unicode codepoint in range 0x00 to 0xD7FF or 0xE000 to 0x10FFFF'); + } + + if ($code <= 0x7F) { + return chr($code); // 0-7,0-F + } elseif ($code <= 0x7FF) { + return chr(0b11000000 + ($code >> 6)) // 0xC-D,0-F,8-B,0-9 + . chr(128 + ($code & 63)); + } elseif ($code <= 0xFFFF) { + return chr(0b11100000 + ($code >> 12)) // 0xE,0-F,8-B,0-9,8-B,0-9 + . chr(128 + (($code >> 6) & 63)) + . chr(128 + ($code & 63)); + } else { + return chr(0b11110000 + ($code >> 18)) // 0xF,0-7,8-B,0-9,8-B,0-9,8-B,0-9 + . chr(128 + (($code >> 12) & 63)) + . chr(128 + (($code >> 6) & 63)) + . chr(128 + ($code & 63)); + } } public static function ord(string $ch): int diff --git a/tests/src/common/Str.phpt b/tests/src/common/Str.phpt index f37f38c6..768fd088 100644 --- a/tests/src/common/Str.phpt +++ b/tests/src/common/Str.phpt @@ -2,11 +2,46 @@ namespace Dogma\Tests\Str; +use Dogma\InvalidValueException; use Dogma\Str; use Dogma\Tester\Assert; require_once __DIR__ . '/../bootstrap.php'; +chr: +Assert::same(Str::chr(0x000000), "\x00"); +Assert::same(Str::chr(0x00007F), "\x7F"); +Assert::same(Str::chr(0x000080), "\u{80}"); +Assert::same(Str::chr(0x0007FF), "\u{7FF}"); +Assert::same(Str::chr(0x000800), "\u{800}"); +Assert::same(Str::chr(0x00D7FF), "\u{D7FF}"); +Assert::same(Str::chr(0x00E000), "\u{E000}"); +Assert::same(Str::chr(0x00FFFF), "\u{FFFF}"); +Assert::same(Str::chr(0x010000), "\u{10000}"); +Assert::same(Str::chr(0x10FFFF), "\u{10FFFF}"); + +foreach ([-1, 0xD800, 0xDFFF, 0x110000] as $code) { + Assert::exception( + static function() use ($code): void { + Str::chr($code); + }, + InvalidValueException::class + ); +} + + +ord: +Assert::same(Str::ord("\x00"), 0x000000); +Assert::same(Str::ord("\x7F"), 0x00007F); +Assert::same(Str::ord("\u{80}"), 0x000080); +Assert::same(Str::ord("\u{7FF}"), 0x0007FF); +Assert::same(Str::ord("\u{800}"), 0x000800); +Assert::same(Str::ord("\u{D7FF}"), 0x00D7FF); +Assert::same(Str::ord("\u{E000}"), 0x00E000); +Assert::same(Str::ord("\u{FFFF}"), 0x00FFFF); +Assert::same(Str::ord("\u{10000}"), 0x010000); +Assert::same(Str::ord("\u{10FFFF}"), 0x10FFFF); + between: Assert::same(Str::between('abc@def#ghi', '@', '#'), 'def');