У меня проблема с удалением из строки символов, отличных от utf8, которые не отображаются должным образом. Символы такие: 0x97 0x61 0x6C 0x6F (шестнадцатеричное представление)
Как лучше всего их удалить? Регулярное выражение или что-то еще?
У меня проблема с удалением из строки символов, отличных от 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.
$regex = <<<'END'
PHP <5.3.x?
elseif (!empty($captures([2])) {
и вы должны использовать !== ""
вместо empty, так как "0"
это считается пустым. Также эта функция работает очень медленно, можно ли это сделать быстрее?
Если вы примените 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
Скачать:
Вы можете использовать mbstring:
$text = mb_convert_encoding($text, 'UTF-8', 'UTF-8');
... удалит недопустимые символы.
<0x1a>
<0x1a>
, хотя и не печатаемый символ, это вполне допустимая последовательность UTF-8. У вас могут быть проблемы с непечатаемыми символами? Проверьте это: stackoverflow.com/questions/1176904/…
ini_set('mbstring.substitute_character', 'none');
иначе в результате я получал вопросительные знаки.
Эта функция удаляет все символы НЕ 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?
í
символ в поле адреса, который является допустимым символом UTF-8 ( см. Таблицу) . Моральный дух: не доверяйте сообщениям об ошибках API :)
$text = iconv("UTF-8", "UTF-8//IGNORE", $text);
Это то, что я использую. Кажется, работает очень хорошо. Взято с http://planetozh.com/blog/2005/01/remove-invalid-characters-in-utf-8/
попробуй это:
$string = iconv("UTF-8","UTF-8//IGNORE",$string);
Согласно руководству iconv , функция будет принимать первый параметр как кодировку ввода, второй параметр как кодировку вывода, а третий как фактическую строку ввода.
Если вы установите как входную, так и выходную кодировку в UTF-8 и добавите //IGNORE
флаг к выходной кодировке, функция отбросит (уберет) все символы во входной строке, которые не могут быть представлены выходной кодировкой. Таким образом, действует фильтрация входной строки.
//IGNORE
, похоже, он не подавляет уведомление о том, что присутствует недопустимый UTF-8 (о котором, конечно, я знаю и хочу исправить). Комментарий в руководстве с высокой оценкой, кажется, считает, что это была ошибка в течение нескольких лет.
iconv
. @halfer Может быть, ваши входные данные не из UTF-8. Другой вариант - выполнить повторное преобразование в ascii, а затем снова обратно в utf-8. В моем случае я использовал iconv
как$output = iconv("UTF-8//", "ISO-8859-1//IGNORE", $input );
Текст может содержать символы, отличные от UTF8 . Попробуйте сначала сделать:
$nonutf8 = mb_convert_encoding($nonutf8 , 'UTF-8', 'UTF-8');
Подробнее об этом можно прочитать здесь: http://php.net/manual/en/function.mb-convert-encoding.php news
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'));
}
Я сделал функцию, которая удаляет недопустимые символы 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()
которые никогда не вернутся. Если кто-то хочет уточнить, почему эта функция работает именно так, я был бы признателен за понимание.
Добро пожаловать в 2019 год и /u
модификатор в регулярном выражении, который будет обрабатывать многобайтовые символы UTF-8 за вас.
Если вы используете только, mb_convert_encoding($value, '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
Вы можете попробовать отправить строки через регулярное выражение с символами за пределами диапазона печати, например \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";
}
php-mbstring
который по умолчанию не упакован в php.
$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'));
Из недавнего патча к модулю парсера 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
, которое в настоящее время не рекомендуется.
ereg_replace()
, извините.
Возможно, не самое точное решение, но оно выполняет свою работу с помощью одной строчки кода:
echo str_replace("?","",(utf8_decode($str)));
utf8_decode
преобразует символы в вопросительный знак;
str_replace
удалит вопросительные знаки.
Таким образом, правила таковы, что первый октлет 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)
Чтобы удалить все символы Unicode за пределами базовой языковой плоскости Unicode:
$str = preg_replace("/[^\\x00-\\xFFFF]/", "", $str);
Немного отличается от вопроса, но я использую HtmlEncode (строка),
здесь псевдокод
var encoded = HtmlEncode(string);
encoded = Regex.Replace(encoded, "&#\d+?;", "");
var result = HtmlDecode(encoded);
ввод и вывод
"Headlight\x007E Bracket, { Cafe Racer<> Style, Stainless Steel 中文呢?"
"Headlight~ Bracket, { Cafe Racer<> Style, Stainless Steel 中文呢?"
Я знаю, что это не идеально, но работает за меня.
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 = '';
}
это работает на нашем сервисе
Как насчет iconv:
http://php.net/manual/en/function.iconv.php
Я не использовал его внутри самого PHP, но он всегда хорошо работал в командной строке. Вы можете заставить его заменять недопустимые символы.