Похоже, что MySQL не поддерживает символы с более чем 3 байтами в своей кодировке UTF-8 по умолчанию.
Итак, в PHP, как я могу избавиться от всех 4 (-и больше) -байтных символов в строке и заменить их чем-то вроде какого-то другого персонажа?
ПРИМЕЧАНИЕ. Вы не должны просто снимать, а заменять с помощью символа U + FFFD для избежания юникодных атак, в основном XSS:
http://unicode.org/reports/tr36/#Deletion_of_Noncharacters
preg_replace('/[\x{10000}-\x{10FFFF}]/u', "\xEF\xBF\xBD", $value);
Поскольку 4-байтовые последовательности UTF-8 всегда начинаются с байтов 0xF0-0xF7
, следующее должно работать:
$str = preg_replace('/[\xF0-\xF7].../s', '', $str);
В качестве альтернативы вы можете использовать preg_replace
в режиме UTF-8, но это, вероятно, будет медленнее:
$str = preg_replace('/[\x{10000}-\x{10FFFF}]/u', '', $str);
Это работает, потому что 4-байтовые последовательности UTF-8 используются для кодовых точек в дополнительных Unicode-плоскостях, начиная с 0x10000
.
Вот пример:
<?php mb_internal_encoding("UTF-8"); //utf8 string, 13 bytes, 9 utf8 chars, 7 ASCII, 1 in latin1, 1 outside the BMP $str = "qué \xF0\x9D\x92\xB3 tal"; $array = mbStringToArray($str); print "str: [$str] strlen:" . strlen($str) . " chars:" . count($array) . "\n"; $str1 = ""; foreach($array as $c) { // print "$c : " . strlen($c) ."\n"; $str1 .= strlen($c)<=3? $c : '?'; } print "[$str1]\n"; function mbStringToArray ($str) { if (empty($str)) return false; $len = mb_strlen($str); $array = array(); for ($i = 0; $i < $len; $i++) { $array[] = mb_substr($str, $i, 1); } return $array; }
Или, немного более компактный и эффективный:
<?php /// mb_internal_encoding("UTF-8"); //utf8 string, 13 bytes, 9 utf8 chars, 7 ASCII, 1 in latin1, 1 outside the BMP $str = "qué \xF0\x9D\x92\xB3 tal"; $str1 = trimOutsideBMP($str); print "original: [$str]\n"; print "trimmed: [$str1]\n"; // Replaces non-BMP characters in the UTF-8 string by a '?' character // Assumes UTF-8 default encoding ( if not sure, call first mb_internal_encoding("UTF-8"); ) function trimOutsideBMP($str) { if (empty($str)) return $str; $len = mb_strlen($str); $str1 = ''; for ($i = 0; $i < $len; $i++) { $c = mb_substr($str, $i, 1); $str1 .= strlen($c) <= 3 ? $c : '?'; } return $str1; }
Пошел на этот вопрос, пытаясь решить мою собственную проблему (Facebook выплескивает некоторые смайлики в виде 4-байтовых символов, Amazon Mechanical Turk не принимает 4-байтовые символы).
Я закончил использовать это, не требует расширения mbstring:
function remove_4_byte($string) { $char_array = preg_split('/(?<!^)(?!$)/u', $string ); for($x=0;$x<sizeof($char_array);$x++) { if(strlen($char_array[$x])>3) { $char_array[$x] = ""; } } return implode($char_array, ""); }
Ниже функция изменяет 3 и 4 байта символов из строки utf8 в '#':
function remove3and4bytesCharFromUtf8Str($str) { return preg_replace('/([\xF0-\xF7]...)|([\xE0-\xEF]..)/s', '#', $str); }
Вот моя реализация, чтобы отфильтровать 4-байтовые символы
$string = preg_replace_callback( '/./u', function (array $match) { return strlen($match[0]) >= 4 ? null : $match[0]; }, $string );
вы можете настроить его и заменить null
(который удаляет символ) с помощью некоторой строки-заменителя. Вы также можете заменить >= 4
на некоторую другую проверку длины байта.