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


112

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

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


1
Перечисленные здесь решения не сработали для меня, поэтому я нашел свой ответ здесь, в разделе «Проверка персонажа»: webcollab.sourceforge.net/unicode.html
bobef

Связанный с этим , но не обязательно дубликат, скорее близкий родственник :)
Уэйн Вейбель

Ответы:


87

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

$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.


что использовать вместо $regex = <<<'END'PHP <5.3.x?
serhio,

Вместо этого вы можете преобразовать их в формат heredoc с небольшим ухудшением читаемости. Другой вариант - использовать строки в одинарных кавычках, но тогда вам придется удалить комментарии.
Маркус Джардерот

В этой строке есть небольшая опечатка, elseif (!empty($captures([2])) {и вы должны использовать !== ""вместо empty, так как "0"это считается пустым. Также эта функция работает очень медленно, можно ли это сделать быстрее?
Кендалл Хопкинс,

2
Это выражение имеет серьезную проблему с памятью, см. Здесь .
Ja͢ck

1
@MarkusJarderot, Regex ....... хм, эта функция готова к производству? Есть ли тестовые примеры для этой функции?
Pacerier

133

Если вы примените 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


13
Замечательный материал! Все другие решения отбрасывают недопустимые символы, но это исправляет. Потрясающие.
giorgio79

4
Вы отлично справились! Раньше я много работал с XML-каналами, и у меня всегда были проблемы с кодированием. Спасибо.
Костанос

5
Я ТЕБЯ ЛЮБЛЮ. Вы сэкономили мне ЧАСЫ работы "bloomoin" над плохими символами UTF8. Спасибо.
Джон Баллинджер

4
Это фантастика. Спасибо
EdgeCaseBerg

2
замечательно, молодец! Рад, что нашел это. Хотел бы я проголосовать с +100 ;-)
Codebeat

61

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

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

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

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


1
@Alliswell какие? Не могли бы вы привести пример?
Frosty Z

конечно,<0x1a>
Alliswell

1
@Alliswell Если я не ошибаюсь <0x1a>, хотя и не печатаемый символ, это вполне допустимая последовательность UTF-8. У вас могут быть проблемы с непечатаемыми символами? Проверьте это: stackoverflow.com/questions/1176904/…
Frosty Z

да, это так. Спасибо друг!
Alliswell

Перед вызовом mb convert мне пришлось установить для символа замены mbstring значение none, ini_set('mbstring.substitute_character', 'none');иначе в результате я получал вопросительные знаки.
cby016 07

22

Эта функция удаляет все символы НЕ 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?

8
Почему имена функций заглавными? Фууу.
Крис Бейкер

5
это ASCII и даже близко не то, что хотел вопрос.
misaxi

1
Этот сработал. Я столкнулся с проблемой, когда API Карт Google сообщил об ошибке из-за «символа, отличного от UTF-8» в URL-адресе запроса API. Виновником был íсимвол в поле адреса, который является допустимым символом UTF-8 ( см. Таблицу) . Моральный дух: не доверяйте сообщениям об ошибках API :)
Валентин Ши

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

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


у меня не сработало. Хотел бы я прикрепить протестированную строку, но, к сожалению, в ней есть недопустимые символы.
Нир О.

3
Извините, после еще нескольких тестов я понял, что это не совсем то, что я думал. Сейчас я использую stackoverflow.com/a/8215387/138023
Знаркус

14

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

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

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

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


Объясните, что делает ваш ответ, вместо того, чтобы сбрасывать фрагмент кода.
Tomasz Kowalczyk

3
Я пробовал это, и //IGNORE, похоже, он не подавляет уведомление о том, что присутствует недопустимый UTF-8 (о котором, конечно, я знаю и хочу исправить). Комментарий в руководстве с высокой оценкой, кажется, считает, что это была ошибка в течение нескольких лет.
Halfer

Всегда лучше использовать iconv. @halfer Может быть, ваши входные данные не из UTF-8. Другой вариант - выполнить повторное преобразование в ascii, а затем снова обратно в utf-8. В моем случае я использовал iconvкак$output = iconv("UTF-8//", "ISO-8859-1//IGNORE", $input );
m3nda

@ erm3nda: я точно не помню свой вариант использования для этого - возможно, разбор веб-сайта UTF-8 был объявлен с неправильной кодировкой. Спасибо за заметку, уверен, что она будет полезна будущему читателю.
Halfer

Да, если вы чего-то не знаете, просто проверьте это и, наконец, вы нажмете клавишу ;-)
m3nda


6

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'));
}

У вас есть три хороших решения, но непонятно, как пользователь выберет одно из них.
Боб Рэй

6

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

Из всех приведенных выше сложных ответов этот помог мне! Спасибо.
Эмин Озлем

Меня смущает эта функция. ord()возвращает результаты в диапазоне 0–255. Гигант ifв этой функции проверяет диапазоны Unicode, ord()которые никогда не вернутся. Если кто-то хочет уточнить, почему эта функция работает именно так, я был бы признателен за понимание.
i336_

4

Добро пожаловать в 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


Добро пожаловать в 2047 год, php-mbstringкоторый по умолчанию не упакован в php.
NVRM


2

Из недавнего патча к модулю парсера JSON каналов Drupal:

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

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

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

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


Это iconvнамного лучше, чем устаревшее, основанное на регулярных выражениях preg_replace, которое в настоящее время не рекомендуется.
m3nda,

3
preg_replace не является устаревшим
Алексей Чекулаев

1
Вы совершенно правы ereg_replace(), извините.
m3nda,

2

Возможно, не самое точное решение, но оно выполняет свою работу с помощью одной строчки кода:

echo str_replace("?","",(utf8_decode($str)));

utf8_decodeпреобразует символы в вопросительный знак;
str_replaceудалит вопросительные знаки.


После того, как вы попробовали сотни решений, единственное, что сработало, - ваше.
Haritsinh Gohil,

1

Таким образом, правила таковы, что первый октлет 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, 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. Однако неясно, какое удаление нужно делать, когда вы получаете искаженный персонаж.


c = (ch << 1)сделает (c & 1)ноль в первый раз, пропуская цикл. Тест, вероятно, должен быть(c & 128)
Маркус Джардерот

1

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

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

0

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

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

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 中文呢?"

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


0
static $preg = <<<'END'
%(
[\x09\x0A\x0D\x20-\x7E]
| [\xC2-\xDF][\x80-\xBF]
| \xE0[\xA0-\xBF][\x80-\xBF]
| [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2}
| \xED[\x80-\x9F][\x80-\xBF]
| \xF0[\x90-\xBF][\x80-\xBF]{2}
| [\xF1-\xF3][\x80-\xBF]{3}
| \xF4[\x80-\x8F][\x80-\xBF]{2}
)%xs
END;
if (preg_match_all($preg, $string, $match)) {
    $string = implode('', $match[0]);
} else {
    $string = '';
}

это работает на нашем сервисе


2
Можете ли вы добавить некоторый контекст, чтобы объяснить, как это ответит на вопрос вместо ответа, содержащего только код.
Арун Винот,

-1

Как насчет iconv:

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

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.