Dizeden utf8 olmayan karakterleri kaldır


112

Düzgün görüntülenmeyen utf8 olmayan karakterleri dizeden kaldırma konusunda sorun yaşıyorum. Karakterler şuna benzer 0x97 0x61 0x6C 0x6F (onaltılık gösterim)

Bunları kaldırmanın en iyi yolu nedir? Normal ifade mi yoksa başka bir şey mi?


1
Burada listelenen çözümler benim için işe yaramadı, bu yüzden cevabımı burada "Karakter doğrulama" bölümünde buldum: webcollab.sourceforge.net/unicode.html
bobef

Bununla ilgili , ancak bir kopya değil, daha çok yakın bir kuzen gibi :)
Wayne Weibel

Yanıtlar:


87

Bir normal ifade yaklaşımı kullanma:

$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 dizilerini arar ve bunları grup 1'de yakalar. Ayrıca UTF-8 dizisinin parçası olarak tanımlanamayan tek baytlarla eşleşir, ancak bunları yakalamaz. Değiştirme, grup 1'de yakalanan şeydir. Bu, tüm geçersiz baytları etkili bir şekilde kaldırır.

Geçersiz baytları UTF-8 karakterleri olarak kodlayarak dizeyi onarmak mümkündür. Ancak hatalar rastgele ise, bu bazı garip semboller bırakabilir.

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

DÜZENLE:

  • !empty(x)boş olmayan değerlerle eşleşir ( "0"boş kabul edilir).
  • x != ""dahil boş olmayan değerlerle eşleşecek "0".
  • x !== ""dışında herhangi bir şeyle eşleşecek "".

x != "" Bu durumda kullanmak için en iyisi gibi görünüyor.

Maçı da biraz hızlandırdım. Her karakteri ayrı ayrı eşleştirmek yerine, geçerli UTF-8 karakter dizileriyle eşleşir.


$regex = <<<'END'PHP <5.3.x için ne kullanılmalı ?
serhio

Okunabilirliğe hafif bir ceza vererek, yorumlu metin formatına dönüştürebilirsiniz. Diğer bir olasılık da tek tırnaklı dizeler kullanmaktır, ancak bu durumda yorumları kaldırmanız gerekecektir.
Markus Jarderot

Bu satırda küçük bir yazım hatası var elseif (!empty($captures([2])) {ve !== ""boş "0"kabul edildiği için boş yerine kullanmalısınız . Ayrıca bu işlev çok yavaştır, bu daha hızlı yapılabilir mi?
Kendall Hopkins

2
Bu ifadenin büyük hafıza sorunu var, buraya bakın .
Ja͢ck

1
@MarkusJarderot, Regex ....... hmm, bu işlev üretime hazır mı? Bu işlev için test senaryoları var mı?
Pacerier

133

utf8_encode()Zaten bir UTF8 dizesine uygularsanız , bozuk bir UTF8 çıktısı döndürür.

Tüm bu konuları ele alan bir fonksiyon yaptım. Denir Encoding::toUTF8().

Dizelerinizin kodlamasının ne olduğunu bilmenize gerek yok. Latin1 (ISO8859-1), Windows-1252 veya UTF8 olabilir veya dize bunların bir karışımına sahip olabilir. Encoding::toUTF8()her şeyi UTF8'e dönüştürür.

Bunu yaptım çünkü bir hizmet bana tamamen karışmış bir veri beslemesi veriyordu, bu kodlamaları aynı dizede karıştırıyordu.

Kullanımı:

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

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

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

Başka bir işlev, Encoding :: fixUTF8 () ekledim, bu işlev, UTF8'e birden çok kez kodlanmış olan bozuk ürün gibi görünen her UTF8 dizesini düzeltir.

Kullanımı:

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

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

Örnekler:

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

çıktı:

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

İndir:

https://github.com/neitanod/forceutf8


13
Olağanüstü şeyler! Diğer tüm çözümler geçersiz karakterleri atar, ancak bu onu düzeltir. Muhteşem.
giorgio79

4
Harika bir iş çıkardın! Geçmişte XML Feed'leriyle çok çalıştım ve her zaman kodlamayla ilgili bir sorun yaşadım. Teşekkür ederim.
Kostanos

5
SENİ SEVİYORUM. Kötü UTF8 karakterlerinde HOURS "bloomoin" çalışması kurtardınız. Teşekkürler.
John Ballinger

4
Bu fantastik. Teşekkürler
EdgeCaseBerg

2
harika, aferin! Bunu bulduğuma sevindim. Keşke +100 ;-) ile oy
verebilseydim

61

Mbstring'i kullanabilirsiniz:

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

... geçersiz karakterleri kaldıracak.

Bkz .: Geçersiz UTF-8 karakterlerini soru işaretleriyle değiştirmek, mbstring.substitute_character göz ardı edilmiş görünüyor


1
@Alliswell hangileri? Lütfen bir örnek verebilir misiniz?
Frosty Z

elbette,<0x1a>
Alliswell

1
@Alliswell Eğer yanılmıyorsam <0x1a>, basılabilir karakter olmasa da, tamamen geçerli bir UTF-8 dizisi. Yazdırılamayan karakterlerle ilgili sorunlarınız olabilir mi?
Frosty Z

evet, durum bu. Teşekkürler dostum!
Alliswell

Mb convert'ü çağırmadan önce, mbstring ikame karakterini none olarak ayarlamak zorunda kaldım, ini_set('mbstring.substitute_character', 'none');aksi takdirde sonuçta soru işaretleri alıyordum.
cby016

21

Bu işlev tüm ASCII OLMAYAN karakterleri kaldırır, yararlıdır, ancak soruyu çözmez:
Bu, kodlamadan bağımsız olarak her zaman çalışan işlevimdir:

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

Nasıl çalışır:

echo remove_bs('Hello õhowå åare youÆ?'); // Hello how are you?

8
Neden tümü büyük harf işlevi adları? Ewww.
Chris Baker

5
ASCII'dir ve sorunun ne istediğine yakın bile değildir.
misaxi

1
Bu çalıştı. Google Maps API, API istek URL'sindeki "UTF-8 olmayan karakter" nedeniyle hatayı bildirdiğinde sorunla karşılaştım. Suçlu, íadres alanında geçerli bir UTF-8 karakteri olan karakterdi, tabloya bakınız . Moral: API hata mesajlarına güvenmeyin :)
Valentine Shi

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

Ben bunu kullanıyorum. Oldukça iyi çalışıyor gibi görünüyor. Alındığı http://planetozh.com/blog/2005/01/remove-invalid-characters-in-utf-8/


benim için çalışmadı. Keşke test edilen satırı ekleyebilseydim, ancak maalesef geçersiz karakterleri var.
Nir O.

3
Üzgünüm, biraz daha test yaptıktan sonra bunun düşündüğüm şeyi gerçekten yapmadığını fark ettim. Şu anda stackoverflow.com/a/8215387/138023
Znarkus

14

bunu dene:

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

İconv kılavuzuna göre , işlev ilk parametreyi giriş karakter kümesi olarak, ikinci parametreyi çıkış karakter kümesi olarak ve üçüncüyü gerçek girdi dizesi olarak alacaktır.

Hem girdi hem de çıktı karakter kümesini UTF-8 olarak ayarlarsanız ve //IGNOREbayrağı çıktı karakter kümesine eklerseniz , işlev, girdi dizesindeki çıktı karakter kümesiyle temsil edilemeyen tüm karakterleri çıkarır (ayırır). Bu nedenle, giriş dizesini filtrelemek etkin.


Bir kod parçacığını atmak yerine cevabınızın ne yaptığını açıklayın.
Tomasz Kowalczyk

3
Bunu denedim ve //IGNOREgeçersiz UTF-8'in mevcut olduğuna dair uyarıyı bastırmıyor gibi görünüyor (ki bunu tabii ki biliyorum ve düzeltmek istiyorum). Kılavuzda yüksek puan alan bir yorum , birkaç yıldır bir hata olduğunu düşünüyor gibi görünüyor.
halfer

Kullanması her zaman daha iyidir iconv. @halfer Belki giriş verileriniz utf-8'den değildir. Diğer bir seçenek de ascii'ye yeniden dönüştürme yapmak ve ardından tekrar utf-8'e geri dönmek. Benim durumumda ben kullanıldığı vermedi iconvgibi$output = iconv("UTF-8//", "ISO-8859-1//IGNORE", $input );
m3nda

@ erm3nda: Bunun için kullanım durumumu tam olarak hatırlamıyorum - yanlış karakter kümesiyle bildirilen bir UTF-8 web sitesini ayrıştırmış olabilirim. Not için teşekkürler, eminim ki gelecekteki okuyucular için faydalı olacaktır.
halfer

Evet, bir şey bilmiyorsanız, sadece test edin ve sonunda tuşuna
basacaksınız


6

UConverter PHP 5.5'ten beri kullanılabilir. Intl uzantı kullanıyorsanız ve mbstring kullanmıyorsanız UConverter daha iyi bir seçimdir.

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'ten beri geçersiz bayt dizisini kaldırmak için kullanılabilir. Htmlspecialchars, büyük bayt boyutunu ve doğruluğunu işlemek için preg_match'ten daha iyidir. Düzenli ifade kullanılarak yapılan birçok yanlış uygulama görülebilir.

function replace_invalid_byte_sequence3($str)
{
    return htmlspecialchars_decode(htmlspecialchars($str, ENT_SUBSTITUTE, 'UTF-8'));
}

Üç güzel çözümünüz var, ancak bir kullanıcının aralarında nasıl seçim yapacağı belli değil.
Bob Ray

6

Bir dizeden geçersiz UTF-8 karakterlerini silen bir işlev yaptım. XML dışa aktarma dosyasını oluşturmadan önce 27000 ürünün açıklamasını temizlemek için kullanıyorum.

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

Yukarıdaki tüm karmaşık cevaplardan, bu benim için hile yaptı! Teşekkürler.
Emin Özlem

Bu işlev kafam karıştı. ord()0-255 aralığındaki sonuçları döndürür. ifBu işlevdeki dev ord(), asla dönmeyecek unicode aralıklarını test eder . Bu işlevin neden bu şekilde çalıştığını açıklığa kavuşturmak isteyen varsa, içgörüyü takdir ediyorum.
i336_

4

2019'a hoş geldiniz ve /usizin için UTF-8 çok baytlı karakterleri işleyecek normal ifadede değiştirici

Yalnızca kullanırsanız mb_convert_encoding($value, 'UTF-8', 'UTF-8'), dizenizde yine de yazdırılamayan karakterlerle karşılaşacaksınız.

Bu yöntem:

  • Tüm geçersiz UTF-8 çok baytlı karakterleri kaldırın mb_convert_encoding
  • Tüm gibi olmayan yazdırılabilir karakter çıkarın \r, \x00(NULL-byte) ile diğer denetim karakterpreg_replace

yöntem:

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

[:print:]tüm yazdırılabilir karakterleri ve \nsatırsonlarını eşleştirin ve diğer her şeyi çıkarın

Aşağıdaki ASCII tablosunu görebilirsiniz .. Yazdırılabilir karakterler 32 ila 127 arasındadır, ancak satırsonu \n, 0 ila 31 arasında değişen kontrol karakterlerinin bir parçasıdır, bu nedenle normal ifadeye yeni satır eklememiz gerekir./[^[:print:]\n]/u

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

\x7F(DEL), \x1B(Esc) vb. Gibi yazdırılabilir aralığın dışındaki karakterlere sahip dizeleri normal ifade aracılığıyla göndermeyi deneyebilir ve bunların nasıl çıkarıldığını görebilirsiniz.

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


php-mbstringVarsayılan olarak php'de paketlenmeyen 2047'ye hoş geldiniz .
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'));

2

En son yamadan Drupal'ın Feeds JSON ayrıştırıcı modülüne:

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

Eğer endişeliyseniz, evet, boşlukları geçerli karakterler olarak tutar.

İhtiyacım olanı yaptım. MySQL'in 'utf8' karakter setine uymayan ve bana "SQLSTATE [HY000]: Genel hata: 1366 Yanlış dize değeri" gibi hatalar veren günümüzde yaygın olan emoji karakterlerini kaldırıyor.

Ayrıntılar için bkz. Https://www.drupal.org/node/1824506#comment-6881382


iconvDayalı eski moda regexp daha iyidir preg_replacegünümüzde artık kullanılmamaktadır wich.
m3nda

3
preg_replace kullanımdan kaldırılmadı
Oleksii Chekulaiev

1
Tamamen haklısın ereg_replace(), özür dilerim.
m3nda

2

Belki en kesin çözüm değil, ancak işi tek bir kod satırıyla hallediyor:

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

utf8_decodekarakterleri soru işaretine dönüştürür;
str_replacesoru işaretlerini kaldıracaktır.


Yüzlerce çözümü denedikten sonra işe yarayan tek çözüm sizindir.
Haritsinh Gohil

1

Dolayısıyla kurallar, ilk UTF-8 sekizlisinin işaretçi olarak yüksek bit setine sahip olması ve daha sonra kaç ek oktlet olduğunu belirtmek için 1 ila 4 bit olmasıdır; daha sonra her ilave oktlet 10'a ayarlanmış yüksek iki bit'e sahip olmalıdır.

Sözde piton şöyle olacaktır:

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

Aynı mantık php'ye çevrilebilir olmalıdır. Bununla birlikte, hatalı biçimlendirilmiş bir karakter elde ettiğinizde ne tür bir sıyırma yapılacağı açık değildir.


c = (ch << 1)(c & 1)Döngüyü atlayarak ilk seferde sıfır yapacak . Test muhtemelen şöyle olmalı(c & 128)
Markus Jarderot

1

Unicode temel dil düzleminin dışındaki tüm Unicode karakterlerini kaldırmak için:

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

0

Sorudan biraz farklı, ancak yaptığım şey HtmlEncode (string) kullanmak,

burada sözde kod

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

giriş ve çıkış

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

Mükemmel olmadığını biliyorum ama benim için iş yapıyor.


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

bizim hizmetimizde çalışıyor


2
Yalnızca kod içeren yanıt yerine bunun soruyu nasıl yanıtlayacağını açıklamak için biraz bağlam ekleyebilir misiniz?
Arun Vinoth

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.