frz*_*bor 7 javascript php unicode character-encoding
我在JS中有一个简单的代码,如果涉及特殊字符,我无法在PHP中复制.
这是JS代码(请参阅JSFiddle输出):
var str = "t??"; //char "t" and special characters, emojis, etc..
document.write("Length is: "+str.length); // Length is: 19
for(var i=0; i<str.length; i++) {
document.write("<br> charCodeAt(" + i + "): " + str.charCodeAt(i));
}
Run Code Online (Sandbox Code Playgroud)
第一个问题是PHP strlen()并且mb_strlen()已经给出了JS的不同结果(strlen:39,mb_strlen:11),但是我设法使用自定义JS_StringLength函数(由于这个 SO答案).
这是我到目前为止在PHP中的内容(请参阅phpFiddle输出):
<?php
function JS_StringLength($string) {
return strlen(iconv('UTF-8', 'UTF-16LE', $string)) / 2;
}
function JS_charCodeAt($str, $index){
//not working!
$char = mb_substr($str, $index, 1, 'UTF-8');
if (mb_check_encoding($char, 'UTF-8'))
{
$ret = mb_convert_encoding($char, 'UTF-32BE', 'UTF-8');
return hexdec(bin2hex($ret));
} else {
return null;
}
}
$str = "t??";
echo $str."\n";
//echo "Length is: ".strlen($str)."\n"; //wrong
echo "Length is: ".JS_StringLength($str)."\n"; //OK
for($i=0; $i<JS_StringLength($str); $i++) {
echo "charCodeAt(".$i."): ".JS_charCodeAt($str, $i)."\n";
}
Run Code Online (Sandbox Code Playgroud)
经过一整天的谷歌搜索,并 尝试 了 我发现的一切,没有任何东西给出与JS相同的结果.什么应该JS_charCodeAt是获得相同输出的JS有类似的表现?
试验#1:将
我的字符串输入https://r12a.github.io/app-conversion/(很棒的东西).看起来JS使用UTF-16代码单元(19)和PHP strlen计算UTF-8代码单元(39).
试验#2:
当json_encode()我在我的字符串上使用时 - 当然 - 结果几乎就是这样,JavaScript可能会使用什么.我甚至检查了json_encode的原始PHP源代码以及json_encode如何转义字符串,但是......好吧..
在标记为重复之前,请确保使用上面示例中的字符串(或随机emojis)测试解决方案,因为在stackoverflow上找到的所有charCodeAt实现都使用大多数特殊字符,但不使用emojis.
JS处理UTF-16的方式并不理想;charCodeAt正在为您挑选代码单元,包括表情符号案例中的代理。如果您想要每个字符的真实代码点,String.codePointAt()将是更好的选择。也就是说,由于您的用例没有被解释,这实现了您最初的要求,而不需要 json 相关的函数:
<?php\n\n$original = 't\xe2\x86\x99\xef\xb8\x8f';\n$converted = iconv('UTF-8', 'UTF-16LE', $original);\n\nfor ($i = 0; $i < iconv_strlen($converted, 'UTF-16LE'); $i++) {\n $character = iconv_substr($converted, $i, 1, 'UTF-16LE');\n $codeUnits = unpack('v*', $character);\n\n foreach ($codeUnits as $codeUnit) {\n echo $codeUnit . PHP_EOL;\n }\n}\nRun Code Online (Sandbox Code Playgroud)\n\n这会将(假定的)UTF-8 字符串转换为 UTF-16,然后循环遍历每个字符。在 UTF-16 中,每个字符的大小为 2 或 4 个字节。使用v重复格式化程序解包将在前一种情况下返回一个短,或在后一种情况中返回 2(v是无符号短格式化程序)。
也可以通过循环UTF-8并逐一转换每个字符来实现;不过,这并没有多大区别。使用 mb_* 函数也可以实现相同的效果。
\n\n编辑
\n\n由于您询问了一种更快的方法,因此将上述方法与 nwellnhof 提供的解决方案相结合可以提供更好的性能:
\n\n<?php\n\n$original = 't\xe2\x86\x99\xef\xb8\x8f';\n$converted = iconv('UTF-8', 'UTF-16LE', $original);\n\nfor ($i = 0; $i < strlen($converted); $i += 2) {\n $codeUnit = ord($converted[$i]) + (ord($converted[$i+1]) << 8);\n echo $codeUnit . PHP_EOL;\n}\nRun Code Online (Sandbox Code Playgroud)\n\n首先,这会将 UTF-8 字符串转换为 UTF-16LE。我们感兴趣的是编写 UTF-16代码单元(根据行为charCodeAt()),这些单元由 16 位表示。该循环只是一次跳转 2 个字节。对于每次迭代,它将获取该位置字节的数值,并将其添加到下一个字节,左移 8。左移是因为我们正在处理 Little Endian 格式的 UTF-16。
举例来说,考虑字符孟加拉数字一 ( \xe0\xa7\xa7)。这由单个 UTF-16 代码单元 表示2535。首先描述如何将其编码为 UTF-16BE 会更容易。该字符的单个代码单元将消耗 16 位:
0000100111100111 (2535)\nRun Code Online (Sandbox Code Playgroud)\n\n在 PHP 中,字符串实际上是字节数组。所以,PHP 将其视为:
\n\n$converted[0] = 00001001 (9)\n$converted[1] = 11100111 (231)\nRun Code Online (Sandbox Code Playgroud)\n\n给定上面的2个字节,我们如何获得代码单元呢?我们真正想做的是:
\n\n 0000100100000000 (2304)\n+ 11100111 (231)\n= 0000100111100111 (2535)\nRun Code Online (Sandbox Code Playgroud)\n\n但我们不能这样做,因为我们只有单个字节可以使用。解决这个问题的一种方法是使用整数,为我们提供完整的 64 位(8 字节)。无论如何,我们希望以整数形式表示代码单元,因此这似乎是一个合理的途径。我们可以通过以下方式获取每个字节的数值ord():
ord($converted[0]) == 0000000000000000000000000000000000000000000000000000000000001001 == 9\nord($converted[1]) == 0000000000000000000000000000000000000000000000000000000011100111 = 231\nRun Code Online (Sandbox Code Playgroud)\n\n将第一个值左移 8:
\n\n 0000000000000000000000000000000000000000000000000000000000001001 (9) \n<< 0000000000000000000000000000000000000000000000000000000000001000 (8)\n= 0000000000000000000000000000000000000000000000000000100100000000 (2304)\nRun Code Online (Sandbox Code Playgroud)\n\n然后像以前一样求和:
\n\n 0000000000000000000000000000000000000000000000000000100100000000 (2304)\n+ 0000000000000000000000000000000000000000000000000000000011100111 (231)\n= 0000000000000000000000000000000000000000000000000000100111100111 (2535)\nRun Code Online (Sandbox Code Playgroud)\n\n现在我们有了正确的代码单元值2535。与 UTF-16LE 的唯一区别是字节顺序相反。因此,我们需要左移第二个字节,而不是将第一个字节左移 8。
PS:执行此步骤的等效方法是
\n\nfor ($i = 0; $i < strlen($converted); $i += 2) {\n $codeUnit = unpack('v', $converted[$i] . $converted[$i+1]);\n echo $codeUnit . PHP_EOL;\n}\nRun Code Online (Sandbox Code Playgroud)\n\n该unpack函数将完全按照所提供的格式化程序所描述的那样执行v,这告诉它期望以小尾数法排列的 16 位。如果您有兴趣优化速度,可能值得对 2 进行基准测试。
| 归档时间: |
|
| 查看次数: |
3546 次 |
| 最近记录: |