Удалить символы не-utf8 из строки

У меня возникла проблема с удалением не-utf8 символов из строки, которые не отображаются должным образом. Символы подобны 0x97 0x61 0x6C 0x6F (шестнадцатеричное представление)

Каков наилучший способ их удаления? Регулярное выражение или что-то еще?

Использование регулярного выражения:

$regex = <<<'END' / ( (?: [\x00-\x7F] # single-byte sequences 0xxxxxxx | [\xC0-\xDF][\x80-\xBF] # double-byte sequences 110xxxxx 10xxxxxx | [\xE0-\xEF][\x80-\xBF]{2} # triple-byte sequences 1110xxxx 10xxxxxx * 2 | [\xF0-\xF7][\x80-\xBF]{3} # quadruple-byte sequence 11110xxx 10xxxxxx * 3 ){1,100} # ...one or more times ) | . # anything else /x END; preg_replace($regex, '$1', $text); 

Он ищет последовательности UTF-8 и захватывает их в группу 1. Он также соответствует одиночным байтам, которые не могут быть идентифицированы как часть последовательности UTF-8, но не фиксируют их. Замена – это то, что было захвачено в группу 1. Это эффективно удаляет все недопустимые байты.

Можно восстановить строку, закодировав недопустимые байты как символы UTF-8. Но если ошибки случайны, это может оставить некоторые странные символы.

 $regex = <<<'END' / ( (?: [\x00-\x7F] # single-byte sequences 0xxxxxxx | [\xC0-\xDF][\x80-\xBF] # double-byte sequences 110xxxxx 10xxxxxx | [\xE0-\xEF][\x80-\xBF]{2} # triple-byte sequences 1110xxxx 10xxxxxx * 2 | [\xF0-\xF7][\x80-\xBF]{3} # quadruple-byte sequence 11110xxx 10xxxxxx * 3 ){1,100} # ...one or more times ) | ( [\x80-\xBF] ) # invalid byte in range 10000000 - 10111111 | ( [\xC0-\xFF] ) # invalid byte in range 11000000 - 11111111 /x END; function utf8replacer($captures) { if ($captures[1] != "") { // Valid byte sequence. Return unmodified. return $captures[1]; } elseif ($captures[2] != "") { // Invalid byte of the form 10xxxxxx. // Encode as 11000010 10xxxxxx. return "\xC2".$captures[2]; } else { // Invalid byte of the form 11xxxxxx. // Encode as 11000011 10xxxxxx. return "\xC3".chr(ord($captures[3])-64); } } preg_replace_callback($regex, "utf8replacer", $text); 

РЕДАКТИРОВАТЬ:

  • !empty(x) будет соответствовать непустым значениям ( "0" считается пустым).
  • x != "" будет соответствовать непустым значениям, включая "0" .
  • x !== "" будет соответствовать чему угодно, кроме "" .

x != "" кажется лучшим в этом случае.

Я немного ускорил матч. Вместо того, чтобы сопоставлять каждый символ отдельно, он соответствует последовательностям действительных символов UTF-8.

Если вы примените utf8_encode() к уже существующей строке UTF8, она вернет искаженный вывод UTF8.

Я сделал функцию, которая затрагивает все эти проблемы. Он называется Encoding::toUTF8() .

Вам не нужно знать, что такое кодировка ваших строк. Это могут быть Latin1 (ISO8859-1), Windows-1252 или UTF8, или строка может содержать их. Encoding::toUTF8() преобразует все в UTF8.

Я сделал это, потому что служба давала мне поток данных, все испорченные, смешивая эти кодировки в одной строке.

Применение:

 require_once('Encoding.php'); use \ForceUTF8\Encoding; // It's namespaced now. $utf8_string = Encoding::toUTF8($mixed_string); $latin1_string = Encoding::toLatin1($mixed_string); 

Я включил еще одну функцию, Encoding :: fixUTF8 (), которая исправит каждую строку UTF8, которая искажает искаженный продукт, который был закодирован в UTF8 несколько раз.

Применение:

 require_once('Encoding.php'); use \ForceUTF8\Encoding; // It's namespaced now. $utf8_string = Encoding::fixUTF8($garbled_utf8_string); 

Примеры:

 echo Encoding::fixUTF8("Fédération Camerounaise de Football"); echo Encoding::fixUTF8("Fédération Camerounaise de Football"); echo Encoding::fixUTF8("FÃÂédÃÂération Camerounaise de Football"); echo Encoding::fixUTF8("Fédération Camerounaise de Football"); 

выведет:

 Fédération Camerounaise de Football Fédération Camerounaise de Football Fédération Camerounaise de Football Fédération Camerounaise de Football 

Скачать:

https://github.com/neitanod/forceutf8

Вы можете использовать mbstring:

 $text = mb_convert_encoding($text, 'UTF-8', 'UTF-8'); 

… удалит недопустимые символы.

См .: Замена недопустимых символов UTF-8 вопросительными знаками, mbstring.substitute_character кажется проигнорированным

Это моя функция, которая всегда работает независимо от кодировки:

 function remove_bs($Str) { $StrArr = str_split($Str); $NewStr = ''; foreach ($StrArr as $Char) { $CharNo = ord($Char); if ($CharNo == 163) { $NewStr .= $Char; continue; } // keep £ if ($CharNo > 31 && $CharNo < 127) { $NewStr .= $Char; } } return $NewStr; } 

Как это работает:

 echo remove_bs('Hello õhowå åare youÆ?'); // Hello how are you? 
 $text = iconv("UTF-8", "UTF-8//IGNORE", $text); 

Это то, что я использую. Кажется, работает очень хорошо. Взято с http://planetozh.com/blog/2005/01/remove-invalid-characters-in-utf-8/

UConverter можно использовать с PHP 5.5. UConverter лучше выбирать, если вы используете расширение intl и не используете mbstring.

 function replace_invalid_byte_sequence($str) { return UConverter::transcode($str, 'UTF-8', 'UTF-8'); } function replace_invalid_byte_sequence2($str) { return (new UConverter('UTF-8', 'UTF-8'))->convert($str); } 

htmlspecialchars можно использовать для удаления неверной последовательности байтов с PHP 5.4. Htmlspecialchars лучше, чем preg_match для обработки большого размера байта и точности. Можно увидеть много неправильной реализации, используя регулярное выражение.

 function replace_invalid_byte_sequence3($str) { return htmlspecialchars_decode(htmlspecialchars($str, ENT_SUBSTITUTE, 'UTF-8')); } 

попробуй это:

 $string = iconv("UTF-8","UTF-8//IGNORE",$string); 

Согласно руководству iconv , функция будет принимать первый параметр в качестве входной кодировки, второй параметр в качестве выходной кодировки, а третий – как фактическую строку ввода.

Если вы установите как кодировку ввода и вывода в UTF-8 , так и добавьте флаг //IGNORE в выходной набор символов, функция отбросит (разделит) все символы в строке ввода, которые не могут быть представлены выходной кодировкой. Таким образом, фильтрация входной строки в действии.

Текст может содержать символ не-utf8 . Попробуйте сделать сначала:

 $nonutf8 = mb_convert_encoding($nonutf8 , 'UTF-8', 'UTF-8'); 

Вы можете узнать больше об этом здесь: http://php.net/manual/en/function.mb-convert-encoding.php news

 $string = preg_replace('~&([az]{1,2})(acute|cedil|circ|grave|lig|orn|ring|slash|th|tilde|uml);~i', '$1', htmlentities($string, ENT_COMPAT, 'UTF-8')); 

Я создал функцию, которая удаляет недопустимые символы UTF-8 из строки. Я использую его, чтобы очистить описание 27000 продуктов, прежде чем он создаст файл экспорта XML.

 public function stripInvalidXml($value) { $ret = ""; $current; if (empty($value)) { return $ret; } $length = strlen($value); for ($i=0; $i < $length; $i++) { $current = ord($value{$i}); if (($current == 0x9) || ($current == 0xA) || ($current == 0xD) || (($current >= 0x20) && ($current <= 0xD7FF)) || (($current >= 0xE000) && ($current <= 0xFFFD)) || (($current >= 0x10000) && ($current <= 0x10FFFF))) { $ret .= chr($current); } else { $ret .= ""; } } return $ret; } 

Из последнего патча на Drupal's Feeds Модуль JSON-парсера:

 //remove everything except valid letters (from any language) $raw = preg_replace('/(?:\\\\u[\pL\p{Zs}])+/', '', $raw); 

Если вы обеспокоены да, он сохраняет пробелы как действительные символы.

Сделал то, что мне было нужно. Он удаляет широко распространенные в настоящее время символы эмози, которые не вписываются в набор символов «utf8» MySQL, и это давало мне такие ошибки, как «SQLSTATE [HY000]: Общая ошибка: 1366 Неверное строковое значение».

Для получения дополнительной информации см. https://www.drupal.org/node/1824506#comment-6881382

Таким образом, правила состоят в том, что первый октет UTF-8 имеет высокий бит, установленный как маркер, а затем от 1 до 4 бит, чтобы указать, сколько дополнительных октетов; то каждый из дополнительных октов должен иметь два старших бита, равные 10.

Псевдо-питон будет:

 newstring = '' cont = 0 for each ch in string: if cont: if (ch >> 6) != 2: # high 2 bits are 10 # do whatever, eg skip it, or skip whole point, or? else: # acceptable continuation of multi-octlet char newstring += ch cont -= 1 else: if (ch >> 7): # high bit set? c = (ch << 1) # strip the high bit marker while (c & 1): # while the high bit indicates another octlet c <<= 1 cont += 1 if cont > 4: # more than 4 octels not allowed; cope with error if !cont: # illegal, do something sensible newstring += ch # or whatever if cont: # last utf-8 was not terminated, cope 

Эта же логика должна быть переводимой на php. Тем не менее, неясно, какой вид зачистки нужно сделать, как только вы получите искаженный характер.

Чтобы удалить все символы Unicode за пределами базовой плоскости Unicode:

 $str = preg_replace("/[^\\x00-\\xFFFF]/", "", $str); 

Немного отличается от вопроса, но то, что я делаю, это использовать HtmlEncode (string),

псевдокод здесь

 var encoded = HtmlEncode(string); encoded = Regex.Replace(encoded, "&#\d+?;", ""); var result = HtmlDecode(encoded); 

вход и выход

 "Headlight\x007E Bracket, &#123; Cafe Racer<> Style, Stainless Steel 中文呢?" "Headlight~ Bracket, &#123; Cafe Racer<> Style, Stainless Steel 中文呢?" 

Я знаю, что это не идеально, но выполняет эту работу для меня.

Как насчет iconv:

http://php.net/manual/en/function.iconv.php

Не использовали его внутри самого PHP, но он всегда хорошо работал для меня в командной строке. Вы можете заставить его заменить недопустимые символы.