Hapus karakter non-utf8 dari string

112

Saya mengalami masalah dengan menghapus karakter non-utf8 dari string, yang tidak ditampilkan dengan benar. Karakternya seperti ini 0x97 0x61 0x6C 0x6F (representasi hex)

Apa cara terbaik untuk menghapusnya? Ekspresi reguler atau yang lainnya?

Dan Sosedoff
sumber
1
Solusi yang tercantum di sini tidak berhasil untuk saya, jadi saya menemukan jawaban saya di sini di bagian "Validasi karakter": webcollab.sourceforge.net/unicode.html
bobef
Terkait dengan ini , tetapi belum tentu duplikat, lebih seperti sepupu dekat :)
Wayne Weibel

Jawaban:

87

Menggunakan pendekatan regex:

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

Ini mencari urutan UTF-8, dan menangkapnya ke dalam grup 1. Ini juga cocok dengan byte tunggal yang tidak dapat diidentifikasi sebagai bagian dari urutan UTF-8, tetapi tidak menangkapnya. Penggantian adalah apa pun yang ditangkap ke dalam grup 1. Ini secara efektif menghapus semua byte yang tidak valid.

Hal ini dimungkinkan untuk memperbaiki string, dengan mengkodekan byte yang tidak valid sebagai karakter UTF-8. Tetapi jika kesalahannya acak, ini dapat meninggalkan beberapa simbol aneh.

$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)akan cocok dengan nilai yang tidak kosong ( "0"dianggap kosong).
  • x != ""akan cocok dengan nilai yang tidak kosong, termasuk "0".
  • x !== ""akan cocok dengan apapun kecuali "".

x != "" tampaknya yang terbaik untuk digunakan dalam kasus ini.

Saya juga telah sedikit mempercepat pertandingan. Alih-alih mencocokkan setiap karakter secara terpisah, ini mencocokkan urutan karakter UTF-8 yang valid.

Markus Jarderot
sumber
apa yang harus digunakan $regex = <<<'END'untuk PHP <5.3.x?
serhio
Anda dapat mengonversinya ke format heredoc sebagai gantinya, dengan sedikit penalti untuk keterbacaan. Kemungkinan lain adalah menggunakan string kutipan tunggal, tetapi kemudian Anda harus menghapus komentar tersebut.
Markus Jarderot
Ada kesalahan ketik kecil di baris ini elseif (!empty($captures([2])) {dan Anda harus menggunakan !== ""sebagai pengganti kosong karena "0"dianggap kosong. Juga fungsi ini sangat lambat, dapatkah ini dilakukan lebih cepat?
Kendall Hopkins
2
Ekspresi ini memiliki masalah memori utama, lihat di sini .
Ja͢ck
1
@ MarkusJarderot, Regex ....... hmm, apakah fungsi ini siap produksi? Apakah ada kasus uji untuk fungsi ini?
Pacerier
132

Jika Anda menerapkan utf8_encode()ke string yang sudah UTF8, itu akan mengembalikan keluaran UTF8 yang kacau.

Saya membuat fungsi yang menangani semua masalah ini. Ini disebut Encoding::toUTF8().

Anda tidak perlu tahu apa pengkodean string Anda. Bisa Latin1 (ISO8859-1), Windows-1252 atau UTF8, atau string dapat memiliki campuran keduanya. Encoding::toUTF8()akan mengubah semuanya menjadi UTF8.

Saya melakukannya karena layanan memberi saya umpan data yang semuanya kacau, mencampur pengkodean tersebut dalam string yang sama.

Pemakaian:

require_once('Encoding.php'); 
use \ForceUTF8\Encoding;  // It's namespaced now.

$utf8_string = Encoding::toUTF8($mixed_string);

$latin1_string = Encoding::toLatin1($mixed_string);

Saya telah menyertakan fungsi lain, Encoding :: fixUTF8 (), yang akan memperbaiki setiap string UTF8 yang terlihat kacau karena telah dikodekan ke UTF8 beberapa kali.

Pemakaian:

require_once('Encoding.php'); 
use \ForceUTF8\Encoding;  // It's namespaced now.

$utf8_string = Encoding::fixUTF8($garbled_utf8_string);

Contoh:

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

akan mengeluarkan:

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

Unduh:

https://github.com/neitanod/forceutf8

Sebastián Grignoli
sumber
13
Barang luar biasa! Semua solusi lain membuang karakter yang tidak valid, tetapi yang ini memperbaikinya. Hebat.
giorgio79
4
Anda melakukan fungsi yang hebat! Saya banyak bekerja dengan XML Feed di masa lalu, dan selalu mengalami masalah dengan encoding. Terima kasih.
Kostanos
5
AKU CINTA KAMU. Anda telah menyelamatkan saya JAM kerja "bloomoin" pada karakter UTF8 yang buruk. Terima kasih.
John Ballinger
4
Ini fantastis. Terima kasih
EdgeCaseBerg
2
Hebat, bagus sekali! Senang saya menemukan ini. Saya berharap saya dapat memberikan suara dengan +100 ;-)
Codebeat
61

Anda dapat menggunakan mbstring:

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

... akan menghapus karakter yang tidak valid.

Lihat: Mengganti karakter UTF-8 yang tidak valid dengan tanda tanya, mbstring.substitute_character tampaknya diabaikan

Frosty Z
sumber
1
@Alliswell yang mana? Bisakah Anda memberikan contoh?
Frosty Z
tentu,<0x1a>
Alliswell
1
@ Alliswell Jika saya tidak salah <0x1a>, meskipun bukan karakter yang dapat dicetak, adalah urutan UTF-8 yang benar-benar valid. Anda mungkin memiliki masalah dengan karakter yang tidak dapat dicetak? Periksa ini: stackoverflow.com/questions/1176904/…
Frosty Z
ya, itu masalahnya. Terima kasih, sobat!
Alliswell
Sebelum memanggil mb convert, saya harus mengatur karakter pengganti mbstring menjadi tidak ada ini_set('mbstring.substitute_character', 'none');jika tidak saya mendapatkan tanda tanya pada hasilnya.
cby016
21

Fungsi ini menghapus semua karakter NON ASCII, ini berguna tetapi tidak menyelesaikan pertanyaan:
Ini adalah fungsi saya yang selalu berfungsi, terlepas dari pengkodeannya:

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

Bagaimana itu bekerja:

echo remove_bs('Hello õhowå åare youÆ?'); // Hello how are you?
David D
sumber
8
Mengapa nama fungsi huruf besar semua? Ewww.
Chris Baker
5
itu adalah ASCII dan bahkan tidak mendekati apa yang diinginkan pertanyaan tersebut.
misaxi
1
Yang ini berhasil. Saya menghadapi masalah ketika Google Maps API melaporkan kesalahan karena 'karakter non-UTF-8' di URL permintaan API. Pelakunya adalah íkarakter di bidang alamat yang merupakan karakter UTF-8 yang valid lihat tabel . Moral: jangan percaya pesan kesalahan API :)
Valentine Shi
17
$text = iconv("UTF-8", "UTF-8//IGNORE", $text);

Inilah yang saya gunakan. Sepertinya bekerja dengan cukup baik. Diambil dari http://planetozh.com/blog/2005/01/remove-invalid-characters-in-utf-8/

Znarkus
sumber
tidak berhasil untuk saya. Saya berharap saya bisa melampirkan baris yang diuji, tetapi sayangnya itu memiliki karakter yang tidak valid.
Nir O.
3
Maaf, setelah beberapa pengujian saya menyadari ini tidak benar-benar melakukan apa yang saya pikirkan. Saya sekarang menggunakan stackoverflow.com/a/8215387/138023
Znarkus
14

coba ini:

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

Menurut manual iconv , fungsi tersebut akan mengambil parameter pertama sebagai rangkaian karakter input, parameter kedua sebagai rangkaian karakter keluaran, dan yang ketiga sebagai string input aktual.

Jika Anda menyetel rangkaian rangkaian karakter masukan dan keluaran ke UTF-8 , dan menambahkan //IGNOREtanda ke rangkaian rangkaian keluaran, fungsi akan menghapus (menghapus) semua karakter dalam string masukan yang tidak dapat diwakili oleh rangkaian rangkaian keluaran. Jadi, pemfilteran string input berlaku.

technoarya
sumber
Jelaskan apa yang dilakukan jawaban Anda daripada membuang cuplikan kode.
Tomasz Kowalczyk
3
Saya telah mencoba ini, dan //IGNOREtampaknya tidak menekan pemberitahuan bahwa ada UTF-8 yang tidak valid (yang, tentu saja, saya ketahui, dan ingin saya perbaiki). Sebuah komentar berperingkat tinggi di manual tampaknya menganggapnya sebagai bug selama beberapa tahun.
halfer
Selalu lebih baik untuk digunakan iconv. @halfer Mungkin data masukan Anda bukan dari utf-8. Pilihan lainnya adalah melakukan konversi ulang ke ascii lalu kembali ke utf-8 lagi. Dalam kasus saya, saya pernah menggunakan iconvseperti$output = iconv("UTF-8//", "ISO-8859-1//IGNORE", $input );
m3nda
@ erm3nda: Saya sama sekali tidak ingat kasus penggunaan saya untuk ini - mungkin telah mengurai situs web UTF-8 yang dideklarasikan dengan charset yang salah. Terima kasih atas catatannya, saya yakin itu akan berguna untuk pembaca yang akan datang.
halfer
Ya, jika Anda tidak tahu sesuatu, coba saja dan akhirnya Anda akan menekan kuncinya ;-)
m3nda
6

UConverter dapat digunakan sejak PHP 5.5. UConverter adalah pilihan yang lebih baik jika Anda menggunakan ekstensi intl dan tidak menggunakan 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 dapat digunakan untuk menghapus urutan byte yang tidak valid sejak PHP 5.4. Htmlspecialchars lebih baik daripada preg_match untuk menangani ukuran byte yang besar dan akurasi. Banyak implementasi yang salah dengan menggunakan ekspresi reguler dapat dilihat.

function replace_invalid_byte_sequence3($str)
{
    return htmlspecialchars_decode(htmlspecialchars($str, ENT_SUBSTITUTE, 'UTF-8'));
}
masakielastik
sumber
Anda memiliki tiga solusi bagus, tetapi tidak jelas bagaimana pengguna akan memilih di antara mereka.
Bob Ray
6

Saya telah membuat fungsi yang menghapus karakter UTF-8 yang tidak valid dari sebuah string. Saya menggunakannya untuk menghapus deskripsi 27000 produk sebelum menghasilkan file ekspor 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;
}
mumin
sumber
Dari semua jawaban kompleks di atas, yang satu ini berhasil untuk saya! Terima kasih.
Emin Özlem
Saya bingung dengan fungsi ini. ord()mengembalikan hasil dalam kisaran 0-255. Raksasa ifdalam fungsi ini menguji rentang unicode yang ord()tidak akan pernah kembali. Jika ada yang ingin menjelaskan mengapa fungsi ini bekerja seperti itu, saya akan menghargai wawasannya.
i336_
4

Selamat datang di 2019 dan /u pengubah dalam regex yang akan menangani karakter multibyte UTF-8 untuk Anda

Jika Anda hanya menggunakan mb_convert_encoding($value, 'UTF-8', 'UTF-8') Anda masih akan mendapatkan karakter yang tidak dapat dicetak dalam string Anda

Metode ini akan:

  • Hapus semua karakter multibyte UTF-8 yang tidak valid dengan mb_convert_encoding
  • Hapus semua karakter yang tidak dapat dicetak seperti \r, \x00(NULL-byte) dan karakter kontrol lainnya denganpreg_replace

metode:

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

[:print:] cocokkan semua karakter yang dapat dicetak dan \n baris baru dan hapus yang lainnya

Anda dapat melihat tabel ASCII di bawah ini .. Karakter yang dapat dicetak berkisar dari 32 hingga 127, tetapi baris baru \nadalah bagian dari karakter kontrol yang berkisar dari 0 hingga 31 sehingga kita harus menambahkan baris baru ke regex/[^[:print:]\n]/u

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

Anda dapat mencoba mengirim string melalui regex dengan karakter di luar rentang yang dapat dicetak seperti \x7F(DEL), \x1B(Esc) dll. Dan lihat bagaimana mereka dilucuti

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

Clarkk
sumber
Selamat datang di tahun 2047, di mana php-mbstringtidak dikemas dalam php secara default.
NVRM
3
$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'));
Alix Axel
sumber
2

Dari patch terbaru ke modul parser JSON Feed Drupal:

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

Jika Anda khawatir ya itu mempertahankan spasi sebagai karakter yang valid.

Melakukan apa yang saya butuhkan. Ini menghapus karakter emoji yang tersebar luas saat ini yang tidak sesuai dengan kumpulan karakter 'utf8' MySQL dan yang memberi saya kesalahan seperti "SQLSTATE [HY000]: Kesalahan umum: 1366 Nilai string salah".

Untuk detailnya, lihat https://www.drupal.org/node/1824506#comment-6881382

Oleksii Chekulaiev
sumber
Ini iconvjauh lebih baik daripada yang berbasis regexp kuno preg_replace, yang sudah usang saat ini.
m3nda
3
preg_replace tidak usang
Oleksii Chekulaiev
1
Anda benar sekali, adalah ereg_replace(), maaf.
m3nda
2

Mungkin bukan solusi yang paling tepat, tetapi ini menyelesaikan pekerjaan dengan satu baris kode:

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

utf8_decodeakan mengubah karakter menjadi tanda tanya;
str_replaceakan menghapus tanda tanya.

pengguna12602477
sumber
Setelah mencoba ratusan solusi, satu-satunya solusi yang berhasil adalah milik Anda.
Haritsinh Gohil
1

Jadi aturannya adalah bahwa oktlet UTF-8 pertama memiliki set bit tinggi sebagai penanda, dan kemudian 1 hingga 4 bit untuk menunjukkan berapa banyak tambahan oktlet; kemudian masing-masing oktlet tambahan harus memiliki dua bit tinggi yang disetel ke 10.

Pseudo-python adalah:

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

Logika yang sama ini harus dapat diterjemahkan ke php. Namun, tidak jelas jenis pengupasan apa yang harus dilakukan setelah Anda mendapatkan karakter yang cacat.

Akan
sumber
c = (ch << 1)akan membuat (c & 1)nol untuk pertama kalinya, melewati loop. Tesnya mungkin(c & 128)
Markus Jarderot
1

Untuk menghapus semua karakter Unicode di luar bidang bahasa dasar Unicode:

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

Sedikit berbeda dengan pertanyaannya, tetapi yang saya lakukan adalah menggunakan HtmlEncode (string),

kode semu di sini

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

masukan dan keluaran

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

Saya tahu ini tidak sempurna, tetapi berhasil untuk saya.

misaxi
sumber
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 = '';
}

itu bekerja pada layanan kami

llluo
sumber
2
Dapatkah Anda menambahkan beberapa konteks untuk menjelaskan bagaimana ini akan menjawab pertanyaan, bukan hanya jawaban kode.
Arun Vinoth
-1

Bagaimana dengan iconv:

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

Belum pernah menggunakannya di dalam PHP itu sendiri tetapi selalu berfungsi dengan baik untuk saya di baris perintah. Anda bisa mendapatkannya untuk menggantikan karakter yang tidak valid.

Ben
sumber