Unicode — длина строки, содержащей эмодзи / специальные символы

Я создаю API для мобильного приложения, и у меня, похоже, есть проблема с подсчетом длины строки, содержащей смайлики. Мой код:

$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 Phone, это 17 везде. Фрагмент iOS (swift):

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

Нам нужно использовать NSString из-за библиотеки. Мне нужно это, чтобы получить начальную и конечную позицию «@mention». Если строка содержит только текст или только смайлики, она работает нормально, поэтому, возможно, возникла проблема со смешанным содержимым.

Что я делаю неправильно? Какую еще информацию я могу предоставить вам, ребята, чтобы направить меня в правильном направлении?

Спасибо!

8

Решение

Все ваши функции учитывают разные вещи.

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') подсчитывает количество кодовых точек (символов Unicode) в строке, когда ее байты декодируются в символы с использованием кодировки UTF-8: 14.

(Другой пример подсчета в значительной степени не имеет смысла, поскольку он основан на обработке входной строки как одной кодировки, когда фактически она содержит данные в другой кодировке.)

NSStrings изначально считаются как кодовые единицы UTF-16. Их 17, а не 14, потому что приведенная выше строка содержит такие символы, как �� которые не помещаются в одну кодовую единицу UTF-16, поэтому должны быть закодированы как суррогатная пара. Нет никаких функций, которые будут подсчитывать строки в кодовых единицах UTF-16 в PHP, но поскольку каждая кодовая единица кодируется в два байта, вы можете решить это достаточно легко, кодировав в UTF-16 и разделив число байтов на два:

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

(Обратите внимание le необходимо сделать суффикс iconv закодировать с определенным порядком байтов UTF-16 и не сбивать счет, выбрав одну и добавив спецификацию в начало строки, чтобы сказать, какую из них он выбрал.)

14

Другие решения

Я включил картинку, чтобы проиллюстрировать ответ, который дал @bobince.

По сути, все кодовые точки несуррогатной пары заканчиваются как два байта в UTF-16, в то время как все кодовые точки суррогатной пары заканчиваются четырьмя байтами. Если мы разделим это на два, мы получим эквивалентное ожидаемое значение длины.

Постскриптум Пожалуйста, прости ошибку в изображении, где написано «кодовые точки» и должно быть написано «кодовые единицы»

разбивка юникода

4

По вопросам рекламы [email protected]