我在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));
}
第一个问题是PHP strlen()
和mb_strlen()
已经提供了与JS (strlen:39,mb_strlen:11)不同的结果,但是我设法得到了相同的结果使用自定义JS_StringLength
功能(感谢this 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";
}
经过一整天的Google搜索,and trying out everything我发现,没有任何结果与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 escapes strings,但是......好吧..
在标记为重复之前,请确保使用上面示例中的字符串(或随机emojis)测试解决方案,因为在stackoverflow上找到的所有charCodeAt实现都与大多数特殊字符一起使用,但是不是用表情符号。
答案 0 :(得分:2)
好的,差不多两天后,我想我自己也找到了答案。
基本思想是button.addActionListener(thing)
enterbutton.addActionListener(thing)
以一种形式逃避多字节Unicode字符,JS使用它们(如= json_encode()
)进行字符计数,"\ud83d\ude18"
函数等等。所以如果我们JSON对字符串进行编码,我们可以提取一个简单字符数组,并转义多字节字符。这样,我们可以很容易地将原始字符串的字符计为UTF-16代码单元(就像JS一样)。当然,我们可以返回&#34; charCodeAt&#34;值(charCodeAt
表示简单字符,并将\ uXXXX十六进制转换为多字节字符处的dec。)
问题:如果我想获得&#34; JS charCodeAt&#34; for循环中每个字符的值(所以基本上将字符串转换为charcode列表),这段代码在长文本上会很慢,因为ord()
中的preg_match_all
将为每个字符运行一次。 />
解决方法:不是每次调用getUTF16CodeUnits
,而是将matches数组存储在变量中,然后使用它。更多详情:FASTER VERSION (backup)
代码和demo:
getUTF16CodeUnits
我们非常感谢改进,修复和评论!
答案 1 :(得分:1)
JS处理UTF-16的方式并不理想; charCodeAt
正在为您挑选代码单元,包括表情符号中的代理。如果你想要每个角色的真实代码点,String.codePointAt()
将是更好的选择。也就是说,由于您的用例没有得到解释,这可以实现您最初的要求而无需json相关功能:
<?php
$original = 't↙️';
$converted = iconv('UTF-8', 'UTF-16LE', $original);
for ($i = 0; $i < iconv_strlen($converted, 'UTF-16LE'); $i++) {
$character = iconv_substr($converted, $i, 1, 'UTF-16LE');
$codeUnits = unpack('v*', $character);
foreach ($codeUnits as $codeUnit) {
echo $codeUnit . PHP_EOL;
}
}
这会将(假设的)UTF-8字符串转换为UTF-16,然后遍历每个字符。在UTF-16中,每个字符的大小为2或4个字节。使用v
重复格式化器解包将在前一种情况下返回一个short,或者在后一种情况下返回2(v
是无符号短格式器)。
也可以通过循环遍历UTF-8并逐个转换每个字符来实现;但它并没有产生很大的不同。使用mb_ *函数也可以实现相同的效果。
修改的
由于您已经询问了更快的方法,将上述内容与nwellnhof提供的解决方案相结合可以提供更好的性能:
<?php
$original = 't↙️';
$converted = iconv('UTF-8', 'UTF-16LE', $original);
for ($i = 0; $i < strlen($converted); $i += 2) {
$codeUnit = ord($converted[$i]) + (ord($converted[$i+1]) << 8);
echo $codeUnit . PHP_EOL;
}
首先,这会将UTF-8字符串转换为UTF-16LE。我们对编写UTF-16 代码单元(根据行为charCodeAt()
)感兴趣,并且这些代码由16位表示。循环一次只跳2个字节。对于每次迭代,它将获取该位置的字节的数值,并将其添加到下一个字节,左移8位。左移是因为我们正在处理小端格式化的UTF- 16。
举例来说,考虑角色BENGALI DIGIT ONE(১
)。这由单个UTF-16代码单元2535
表示。首先描述如何将其编码为UTF-16BE更容易。该字符的单个代码单元将消耗16位:
0000100111100111 (2535)
在PHP中,字符串实际上是字节数组。因此,PHP将其视为:
$converted[0] = 00001001 (9)
$converted[1] = 11100111 (231)
鉴于上面的2个字节,我们如何获得代码单元?我们真正想做的是:
0000100100000000 (2304)
+ 11100111 (231)
= 0000100111100111 (2535)
但我们无法做到这一点,因为我们只有一个字节可供使用。一种方法是处理这是使用整数,给我们一个完整的64位(8字节)..我们想要以整数形式表示代码单位,所以这似乎是一个合理的路线。我们可以通过ord()
获取每个字节的数值:
ord($converted[0]) == 0000000000000000000000000000000000000000000000000000000000001001 == 9
ord($converted[1]) == 0000000000000000000000000000000000000000000000000000000011100111 = 231
左移第一个值8:
0000000000000000000000000000000000000000000000000000000000001001 (9)
<< 0000000000000000000000000000000000000000000000000000000000001000 (8)
= 0000000000000000000000000000000000000000000000000000100100000000 (2304)
然后像以前一样总结:
0000000000000000000000000000000000000000000000000000100100000000 (2304)
+ 0000000000000000000000000000000000000000000000000000000011100111 (231)
= 0000000000000000000000000000000000000000000000000000100111100111 (2535)
因此我们现在拥有正确的代码单位值2535
。与UTF-16LE的唯一区别是字节的顺序是相反的。因此,我们不需要将第一个字节左移8,而是需要左移第二个字节。
P.S:执行此步骤的等效方法是执行
for ($i = 0; $i < strlen($converted); $i += 2) {
$codeUnit = unpack('v', $converted[$i] . $converted[$i+1]);
echo $codeUnit . PHP_EOL;
}
unpack
函数将完全按照所描述的那样提供v
格式化程序,它告诉它期望16位以小端排列。如果您对优化速度感兴趣,可能值得对2进行基准测试。
答案 2 :(得分:1)
如果确实需要等同于JavaScript的charCodeAt
方法,请尝试:
function JS_charCodeAt($str, $index) {
$utf16 = mb_convert_encoding($str, 'UTF-16LE', 'UTF-8');
return ord($utf16[$index*2]) + (ord($utf16[$index*2+1]) << 8);
}
但charCodeAt
存在问题,应替换为codePointAt
。大多数JavaScript代码处理补充Unicode平面中的字符,如Emojis和使用charCodeAt
可能是错误的。您可以在问题UTF-8 safe equivalent of ord or charCodeAt() in PHP的答案中找到模仿codePointAt
的代码。