У мене виникають проблеми з видаленням символів non-utf8 з рядка, які не відображаються належним чином. Персонажі такі 0x97 0x61 0x6C 0x6F (шістнадцяткове представлення)
Який найкращий спосіб їх видалити? Регулярне вираження чи щось інше?
У мене виникають проблеми з видаленням символів non-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])) {яку слід використовувати !== ""замість порожньої, оскільки "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');інакше, я отримував запитання в результаті.
Ця функція видаляє всі символи 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?
íсимволом у адресному полі, яке є дійсним символом 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у цій функції тестує діапазони унікоду, які ord()ніколи не повернуться. Якщо хтось хоче уточнити, чому ця функція працює так, як вона працює, я би вдячний прозрінню.
Ласкаво просимо до 2019 року та /uмодифікатора в регулярному вираженні, який буде обробляти багатоканальні символи UTF-8 для вас
Якщо ви користуєтесь лише, mb_convert_encoding($value, 'UTF-8', 'UTF-8')ви все одно матимете недруковані символи у рядку
Цей метод:
mb_convert_encoding\r, \x00(NULL-байт) і інші символи управління зpreg_replacefunction 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's Feeds:
//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
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, але він завжди працював добре для мене в командному рядку. Ви можете отримати його для заміни недійсних символів.