如何创建一个包含所有UTF-8字符的字符串?

7

有许多方法来表示一百万个UTF-8字符,例如拉丁大写字母带长音符号的 "A" (Ā)。这个字符的Unicode码位是U+0100,十六进制数字是0xc4 0x80,十进制数字是196 128,二进制数字是11000100 10000000

我想创建一个包含前65,535个UTF-8字符的集合,以用于测试应用程序。这些都是Unicode字符,直到代码点U+FFFF(字节3)。

是否可以使用诸如 for($x=0) 这样的循环,然后将结果转换为另一个基数(比如十六进制),从而创建匹配的Unicode字符?

我可以使用类似以下代码创建值Ā

$char = "\xc4\x80";
// or
$char = chr(196).chr(128);

然而,我不确定如何将此转化为自动化流程。
// fail!
$char = "\x". dechex($a). "\x". dexhex($b);

U+FFFF不符合Unicode字符的定义(U+FFFE也不是,它被用作字节顺序标记)。 - Tim Pietzcker
1
你的问题有点混淆了码点(数字)和编码(字节序列)。更准确的陈述应该是:“这是Unicode码点U+0100(十进制为256),它的UTF-8编码是两个字节:0xc4 0x80(或者十进制为196,128)…” - leonbloy
6个回答

8
你可以利用 iconv (或其他一些函数) 将一个码点数转换为 UTF-8 字符串:
function unichr($i)
{
    return iconv('UCS-4LE', 'UTF-8', pack('V', $i));
}

$codeunits = array();
for ($i = 0; $i<0xD800; $i++)
    $codeunits[] = unichr($i);
for ($i = 0xE000; $i<0xFFFF; $i++)
    $codeunits[] = unichr($i);
$all = implode($codeunits);

我避免使用代理范围0xD800-0xDFFF,因为它们本身不能放在UTF-8中;那会变成“CESU-8”。


1
+1 Bingo。我想这是最好的方法。您可以将每个代码点(整数)打包在32位LE中(这相当于通过手动在UCS-4LE中“编码”它),然后请求iconv将编码转换为UTF-8。(我已经说过PHP不擅长Unicode了吗?) - leonbloy
我不确定。如果有帮助的话,我可以告诉你“PHP在Unicode方面很糟糕”。 - bobince
1
太棒了!我现在拥有一个有用的 UTF-8 字符列表,可以通过正则表达式测试。 - Xeoncross

4
我不确定你能够通过编程实现这一点,主要是因为Unicode代码点和字符之间存在差异。请参见http://www.unicode.org/standard/where,其中列举了一些由代码点组合表示的字符示例。
有些代码点本身没有意义,只能与另一个字符一起使用(例如重音符号)。请参见http://www.unicode.org/charts/charindex.html,其中包含所有“组合”代码点的部分的代码点列表。
此外,对于测试应用程序的使用,除了可能的UTF-8代码点列表之外,您还需要其他内容,即多个无效/格式不正确的UTF-8序列,您的应用程序需要能够从中恢复过来。
为此,请查看Markus Kuhn's Unicode stress test

1

我很快地从C语言翻译了这个,但应该能让你明白意思:

function encodeUTF8( $inValue ) {
    $result = "";

    if ( $inValue < 0x00000080 ) {
        $result .= chr( $inValue );
        $extra = 0;
    } else if ( $inValue < 0x00000800 ) {
        $result .= chr( 0x00C0 | ( ( $inValue >> 6 ) & 0x001F ) );
        $extra = 6;
    } else if ( $inValue < 0x00010000 ) {
        $result .= chr( 0x00E0 | ( ( $inValue >> 12 ) & 0x000F ) );
        $extra = 12;
    } else if ( $inValue < 0x00200000 ) {
        $result .= chr( 0x00F0 | ( ( $inValue >> 18 ) & 0x0007 ) );
        $extra = 18;
    } else if ( $inValue < 0x04000000 ) {
        $result .= chr( 0x00F8 | ( ( $inValue >> 24 ) & 0x0003 ) );
        $extra = 24;
    } else if ( $inValue < 0x80000000 ) {
        $result .= chr( 0x00FC | ( ( $inValue >> 30 ) & 0x0001 ) );
        $extra = 30;
    }

    while ( $extra > 0 ) {
        $result .= chr( 0x0080 | ( ( $inValue >> ( $extra -= 6 ) ) & 0x003F ) );
    }

    return $result;
}

逻辑是正确的,但我不确定php是否正确,请务必检查一下。我从未尝试过像这样使用chr

有很多值您不想编码,例如0xD000-0xDFFF、0xE000-0xF8FF和0xFFF0-0xFFFF,还有几个用于组合字符和保留字符的间隙。


0

当然最后一个不会起作用。\x序列属于双引号字符串。

$char = chr(196).chr(128); 有什么问题?我是指使用 chr($a).chr($b) 这种方式。


0
实际上,在PHP中有一个mb_chr()函数,它可以返回UTF-8编码点对应的字符。

0
<?php

function chr_utf8($n,$f='C*'){
return $n<(1<<7)?chr($n):($n<1<<11?pack($f,192|$n>>6,1<<7|191&$n):
($n<(1<<16)?pack($f,224|$n>>12,1<<7|63&$n>>6,1<<7|63&$n):
($n<(1<<20|1<<16)?pack($f,240|$n>>18,1<<7|63&$n>>12,1<<7|63&$n>>6,1<<7|63&$n):'')));
}

echo implode('',array_map('chr_utf8',range(0,65535)));

// Output a big string, you can increase the range to 1114111…

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接