首页
学习
活动
专区
工具
TVP
发布
社区首页 >问答首页 >JS charCodeAt在PHP中的等价物(完全兼容unicode和emoji )

JS charCodeAt在PHP中的等价物(完全兼容unicode和emoji )
EN

Stack Overflow用户
提问于 2016-11-28 17:40:07
回答 2查看 5.4K关注 0票数 8

我在JS中有一个简单的代码,如果涉及到特殊字符,我不能用PHP复制它。

这是JS代码(输出见JSFiddle ):

代码语言:javascript
复制
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所以answer)。

下面是我到目前为止在PHP中所做的(输出请参阅phpFiddle ):

代码语言:javascript
复制
<?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";
}

经过一整天的谷歌搜索,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,但是..好吧..。

在标记为重复之前,请确保使用上面示例中的字符串(或随机表情)测试解决方案,因为在stackoverflow上找到的所有charCodeAt实现都可以处理大多数特殊字符,但不能使用表情。

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2016-11-29 18:55:48

JS处理UTF-16的方式并不理想;charCodeAt正在为你挑选代码单元,包括表情符号中的代理。如果您想要每个字符的实际代码点,String.codePointAt()将是更好的选择。也就是说,由于没有解释你的用例,这就实现了你最初所要求的,而不需要json相关的函数:

代码语言:javascript
复制
<?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重复格式化程序解包时,在前一种情况下将返回一个短整型,在后一种情况下将返回2 (v是无符号短格式设置程序)。

它也可以通过在UTF-8上循环并逐个转换每个字符来实现;不过,这并没有太大的区别。同样,使用mb_*函数也可以实现同样的效果。

编辑

既然你已经询问过一种更快的方法来做这件事,将上面的方法与nwellnhof提供的解决方案结合起来可以提供更好的性能:

代码语言:javascript
复制
<?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代码单元(根据behaviour charCodeAt()),这些代码单元由16位表示。循环只是一次跳过2个字节。对于每次迭代,它将获取该位置的字节的数值,并将其添加到下一个字节,左移8。左移是因为我们处理的是小端格式的UTF-16。

举个例子,以孟加拉数字一()为例。这由单个UTF-16编码单元2535表示。首先描述如何将其编码为UTF-16BE更容易。该字符的单个代码单元将消耗16位:

代码语言:javascript
复制
0000100111100111 (2535)

在PHP中,字符串实际上是字节数组。因此,PHP认为这是:

代码语言:javascript
复制
$converted[0] = 00001001 (9)
$converted[1] = 11100111 (231)

给定上述两个字节,我们如何获得代码单元?我们真正想做的是:

代码语言:javascript
复制
   0000100100000000 (2304)
+          11100111 (231)
=  0000100111100111 (2535)

但我们不能这样做,因为我们只有一个字节可以玩。解决这个问题的一种方法是使用整数,给我们一个完整的64位(8字节)。而且我们希望以整数形式表示代码单元,因此这似乎是一条合理的路线。我们可以通过ord()获得每个字节的数值

代码语言:javascript
复制
ord($converted[0]) == 0000000000000000000000000000000000000000000000000000000000001001 == 9
ord($converted[1]) == 0000000000000000000000000000000000000000000000000000000011100111 = 231

并将第一个值左移8:

代码语言:javascript
复制
   0000000000000000000000000000000000000000000000000000000000001001 (9) 
<< 0000000000000000000000000000000000000000000000000000000000001000 (8)
=  0000000000000000000000000000000000000000000000000000100100000000 (2304)

然后一起求和,就像前面一样:

代码语言:javascript
复制
   0000000000000000000000000000000000000000000000000000100100000000 (2304)
+  0000000000000000000000000000000000000000000000000000000011100111 (231)
=  0000000000000000000000000000000000000000000000000000100111100111 (2535)

因此,我们现在有了正确的代码单元值2535。与UTF-16LE的唯一区别是字节顺序颠倒。因此,我们需要将第二个字节左移,而不是将第一个字节左移8。

附言:执行此步骤的另一种等效方法是

代码语言:javascript
复制
for ($i = 0; $i < strlen($converted); $i += 2) {
        $codeUnit = unpack('v', $converted[$i] . $converted[$i+1]);
        echo $codeUnit . PHP_EOL;
}

unpack函数将完全按照提供的v格式化程序的描述执行操作,该格式化程序告诉它以小端序排列的16位。如果您对速度优化感兴趣,那么对2进行基准测试可能是值得的。

票数 3
EN

Stack Overflow用户

发布于 2016-11-29 21:43:05

如果您确实想要一个与JavaScript的charCodeAt方法等效的方法,请尝试:

代码语言:javascript
复制
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代替。大多数在辅助Unicode平面中处理字符的JavaScript代码,比如表情符号和使用charCodeAt,可能是错误的。您可以在问题UTF-8 safe equivalent of ord or charCodeAt() in PHP的答案中找到模拟codePointAt的代码。

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/40841149

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档