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

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

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

Ответ 1

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

$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);

EDIT:

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

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

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

Ответ 2

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

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

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

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

Использование:

require_once('Encoding.php'); 
use \ForceUTF8\Encoding;  // It 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 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

Ответ 4

Эта функция удаляет все символы NON ASCII, это полезно, но не решает вопрос:
Это моя функция, которая всегда работает независимо от кодировки:

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?

Ответ 6

попробуйте следующее:

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

В соответствии с iconv manual, функция примет первый параметр в качестве входной кодировки, второй параметр в качестве выходной кодировки и третий как фактическая строка ввода.

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

Ответ 7

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

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

Подробнее об этом можно прочитать здесь: http://php.net/manual/en/function.mb-convert-encoding.php news

Ответ 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'));
}

Ответ 9

Я создал функцию, которая удаляет недопустимые символы 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;
}

Ответ 10

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

Ответ 11

От недавнего патча к Drupal Feeds Модуль JSON-парсера:

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

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

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

Подробнее см. https://www.drupal.org/node/1824506#comment-6881382

Ответ 12

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

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

newstring = ''
cont = 0
for each ch in string:
  if cont:
    if (ch >> 6) != 2: # high 2 bits are 10
      # do whatever, e.g. 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. Тем не менее, неясно, какой вид зачистки нужно делать, как только вы получите неверный характер.

Ответ 13

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

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

Ответ 14

Немного отличается от вопроса, но то, что я делаю, это использовать 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 中文呢?"

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

Ответ 15

Добро пожаловать в 2019 и модификатор /u в регулярных выражениях, который будет обрабатывать многобайтовые символы UTF-8 для вас

Если вы используете только mb_convert_encoding($value, 'UTF-8', 'UTF-8'), у вас все равно останутся непечатаемые символы в вашей строке

Этот метод будет:

  • Удалите все недопустимые многобайтовые символы UTF-8 с помощью mb_convert_encoding
  • Удалите все непечатаемые символы, такие как \r, \x00 (NULL-байт) и другие контрольные символы с помощью preg_replace

Метод:

function utf8_filter(string $value): string{
    return preg_replace('/[^[:print:]\n]/u', '', mb_convert_encoding($value, 'UTF-8', 'UTF-8'));
}

[:print:] сопоставляет все печатные символы и \n переводы строк и удаляет все остальное

Вы можете увидеть таблицу ASCII ниже. Печатные символы варьируются от 32 до 127, но символ новой строки \n является частью контрольных символов, которые варьируются от 0 до 31, поэтому мы должны добавить новую строку в регулярное выражение /[^[:print:]\n]/u

https://cdn.shopify.com/s/files/1/1014/5789/files/Standard-ASCII-Table_large.jpg?10669400161723642407

Вы можете попытаться отправить строки через регулярное выражение с символами за пределами диапазона печати, например \x7F (DEL), \x1B (Esc) и т.д., И посмотреть, как они удаляются

function utf8_filter(string $value): string{
    return preg_replace('/[^[:print:]\n]/u', '', mb_convert_encoding($value, 'UTF-8', 'UTF-8'));
}

$arr = [
    'Danish chars'          => 'Hello from Denmark with æøå',
    'Non-printable chars'   => "\x7FHello with invalid chars\r \x00"
];

foreach($arr as $k => $v){
    echo "$k:\n---------\n";

    $len = strlen($v);
    echo "$v\n(".$len.")\n";

    $strip = utf8_decode(utf8_filter(utf8_encode($v)));
    $strip_len = strlen($strip);
    echo $strip."\n(".$strip_len.")\n\n";

    echo "Chars removed: ".($len - $strip_len)."\n\n\n";
}

https://www.tehplayground.com/q5sJ3FOddhv1atpR

Ответ 16

Как насчет iconv:

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

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