PHP - 包含表情符号/特殊字符

时间:2015-06-02 19:00:29

标签: php unicode unicode-string

我正在为移动应用程序构建API,我似乎在计算包含emojis的字符串的长度时出现问题。我的代码:

$str = "✌️ @mention";

printf("strlen: %d" . PHP_EOL, strlen($str));
printf("mb_strlen UTF-8: %d" . PHP_EOL, mb_strlen($str, "UTF-8"));
printf("mb_strlen UTF-16: %d" . PHP_EOL, mb_strlen($str, "UTF-16"));
printf("iconv UTF-16: %d" . PHP_EOL, iconv_strlen(iconv("UTF-8", "UTF-16", $str)));
printf("iconv UTF-16: %d" . PHP_EOL, iconv_strlen(iconv("ISO-8859-1", "UTF-16", $str)));

这是对此的反应:

strlen: 27
mb_strlen UTF-8: 14
mb_strlen UTF-16: 13
iconv UTF-16: 14
iconv UTF-16: 27

然而我应该得到17作为结果。我们尝试在iOS,Android和Windows手机上使用字符串长度,它到处都是17。 iOS(swift)片段:

var str = "✌️ @mention"
(str as NSString).length // 17
count(str) // 13
count(str.utf16) // 17
count(str.utf8) // 27

由于库,我们需要使用NSString。我需要这个来获得“@mention”的起始位置和结束位置。如果字符串仅包含文本或仅包含表情符号,则它可以正常工作,因此混合内容可能存在一些问题。

我做错了什么?还有什么其他信息可以让你们让我们朝着正确的方向前进?

谢谢!

3 个答案:

答案 0 :(得分:14)

你的功能都在计算不同的东西。

Graphemes:                                       ✌                ️                     @       m      e      n      t      i      o      n    13
                      -----------  -----------  --------  ---------------------  ------ ------ ------ ------ ------ ------ ------ ------ ------
Code points:            U+1F44D      U+1F3FF     U+270C     U+1F3FF     U+FE0F   U+0020 U+0040 U+006D U+0065 U+006E U+0074 U+0069 U+006F U+006E  14
UTF-16 code units:     D83D DC4D    D83C DFFF     270C     D83C DFFF     FE0F     0020   0040   006D   0065   006E   0074   0069   006F   006E   17
UTF-16-encoded bytes: 3D D8 4D DC  3C D8 FF DF   0C 27    3C D8 FF DF   0F FE    20 00  40 00  6D 00  65 00  6E 00  74 00  69 00  6F 00  6E 00   34
UTF-8-encoded bytes:  F0 9F 91 8D  F0 9F 8F BF  E2 9C 8C  F0 9F 8F BF  EF B8 8F    20     40     6D     65     6E     74     69     6F     6E    27

PHP字符串本身是字节。

strlen()计算字符串中的字节数:27。

mb_strlen(..., 'utf-8')计算字符串中使用UTF-8编码将其字节解码为字符时的代码点数(Unicode字符):14。

(其他示例计数在很大程度上没有意义,因为它们基于将输入字符串视为一种编码,而实际上它包含不同编码的数据。)

NSStrings本身被视为UTF-16代码单元。有17个而不是14个,因为上面的字符串包含之类的字符,它们不适合单个UTF-16代码单元,因此必须编码为代理项对。没有任何函数会在PHP中以UTF-16代码单位对字符串进行计数,但由于每个代码单元都编码为两个字节,因此您可以通过编码为UTF-16并将数量除以字节数加两:

strlen(iconv('utf-8', 'utf-16le', $str)) / 2

(注意:le后缀必须使iconv编码为UTF-16的特定字节序,而不是通过选择一个并在BOM的开头添加BOM来计算计数。字符串,说明它选择了哪一个。)

答案 1 :(得分:4)

我附上了一张图片,以帮助说明@bobince给出的答案。

基本上,所有非代理对代码点最终都是UTF-16中的两个字节,而所有代理对代码点最终都是四个字节。如果我们将其除以2,我们得到等效的预期长度值。

P.S。请原谅图像中出现“代码点”的错误,并应说“代码单位”

unicode breakdown

答案 2 :(得分:1)

我知道这是一个老问题,但我最近遇到了这个问题,这可能对其他人也有帮助。

问题中的字符串:

$str = "??✌?️ @mention";

我希望它数为 11:??✌?️{2}、space{1}、@{1}、mention{7} 所以2 + 1 + 1 + 7 = 11

PHP 的 intl 扩展有一个 grapheme_strlen() 函数,它以字素为单位获取字符串长度,而不是字节或字符 (documentation)

<?php
$str = "??✌?️ @mention";
printf("strlen: %d" . PHP_EOL, strlen($str));
printf("mb_strlen UTF-8: %d" . PHP_EOL, mb_strlen($str, "UTF-8"));
printf("mb_strlen UTF-16: %d" . PHP_EOL, mb_strlen($str, "UTF-16"));
printf("iconv UTF-16: %d" . PHP_EOL, iconv_strlen(iconv("UTF-8", "UTF-16", $str)));
printf("iconv UTF-16: %d" . PHP_EOL, iconv_strlen(iconv("ISO-8859-1", "UTF-16", $str)));
printf("grapheme_strlen: %d" . PHP_EOL, grapheme_strlen($str));

PHP 7.4 上的输出:

strlen: 27
mb_strlen UTF-8: 14
mb_strlen UTF-16: 13
PHP Notice:  iconv_strlen(): Detected an illegal character in input string in php shell code on line 1
iconv UTF-16: 0
PHP Notice:  iconv_strlen(): Detected an illegal character in input string in php shell code on line 1
iconv UTF-16: 0
grapheme_strlen: 11

或者在这里试试:https://www.tehplayground.com/s8e40DAslYX4Mozo