PHP: Orijinal karakter setini bilmeden herhangi bir dizeyi UTF-8'e dönüştürün veya en azından deneyin


151

Dünyanın her yerinden müşterilerle ilgilenen bir uygulamam var ve doğal olarak veritabanıma giren her şeyin UTF-8 kodlu olmasını istiyorum.

Benim için asıl sorun, herhangi bir dizenin kaynağının hangi kodlama olacağını bilmememdir - bir metin kutusundan olabilir (kullanmak <form accept-charset="utf-8">yalnızca kullanıcıya gerçekten formu gönderdiyse yararlıdır) veya olabilir yüklenen bir metin dosyasından, bu yüzden gerçekten girdi üzerinde hiçbir kontrolüm yok.

İhtiyacım olan şey, veritabanıma giren şeylerin olabildiğince UTF-8 kodlu olmasını sağlayan bir işlev veya sınıf. Denedim iconv(mb_detect_encoding($text), "UTF-8", $text); ama bunda problemler var (eğer giriş 'nişanlı' ise 'nişanlı' olarak dönüyor). Çok şey denedim = /

Dosya yüklemeleri için, son kullanıcıdan kullandıkları kodlamayı belirlemesini ve çıktının nasıl görüneceğine dair önizlemeleri göstermesini isteme fikrini seviyorum, ancak bu kötü hackerlara karşı yardımcı olmuyor (aslında, hayatlarını değiştirebilir biraz daha kolay).

Konuyla ilgili diğer SO sorularını okudum, ancak hepsinin "RSS beslemelerini ayrıştırmam gerekiyor" veya "Web sitelerinden veri topluyorum" (veya aslında "Yapamazsınız") gibi ince farklılıkları var gibi görünüyor.

Ama en azından denemesi iyi olan bir şey olmalı !


5
Tanım gereği kesinlikle doğru olmak mümkün değildir, gerçekte bilinmeyen bir kodlamayı tahmin etmenin başarı oranı müthiş değildir. Buluşsal yöntem kullanmak mümkündür, ancak% 100'den çok daha az malzemeye bağlı olarak, zamanın% 100'ünden daha az doğru olacaktır . Bunun farkında olmalısın. Belki buradaki biri en azından iyi sezgisel yöntemlere sahip bir kütüphane önerebilir.
deceze

Elbette, mükemmel bir çözüm olmadığını biliyorum - bu yüzden en azından iyi gidecek bir şey arzusu.
Grim ...

bu yardımcı olabilir: stackoverflow.com/q/505562/642173
Melsi

UTF-8//IGNORE2. param olarak kullanmayı denediniz iconvmi?
yangın

Evet, sonunda bunu yaptım. Belli ki 'nişanlısı' 'nişanlı' olduğu için mükemmel değil, ama kesinlikle daha iyi. TRANSLIT nasıl çalışmaz?
Grim ...

Yanıtlar:


261

İstediğin şey son derece zor. Mümkünse, kullanıcının kodlamayı belirtmesini sağlamak en iyisidir. Bir saldırıyı önlemek, bu şekilde çok daha kolay veya daha zor olmamalıdır.

Ancak bunu yapmayı deneyebilirsiniz:

iconv(mb_detect_encoding($text, mb_detect_order(), true), "UTF-8", $text);

Sıkı olarak ayarlamak daha iyi bir sonuç almanıza yardımcı olabilir.


5
Lütfen, mb_detect_encodingphp dağıtımınızdaki kaynak koduna bir göz atın (burada bir yerde: ext / mbstring / libmbfl / mbfl / mbfl_ident.c). Bu işlev hiç düzgün çalışmıyor. Hatta bazı kodlamalar için "dönüş doğru", lol vardır. Diğerleri Ctrl + c Ctrl + v işlevlerindedir. Bunun nedeni, bir tür sözlük veya istatistik yaklaşımı olmadan (benimki gibi) kodlamayı tespit edemezsiniz.
Oroboros102

1
Anladığım kadarıyla, mb_detect_encodingsağlanan kodlamalar listesinden geçiyor ve dizede geçersiz bayt dizileri olmayan ilkini kabul ediyorum ... ISO-8859-1 gibi geçersiz bayt dizileri olmayan kodlamalar için her zaman doğrudur . "Akıllı" buluşsal yöntemler yoktur ve sonuçlar, geçtiğiniz kodlamaların listesine (ve sırasına) göre büyük ölçüde değişir.
wutz

Bu benim için çalışıyor gibi görünüyor. Kullanıcılarım bir utf8 sayfasında tinymce ile metin gönderiyorlardı, ancak bilinmeyen bir nedenden ötürü utf8 olmayan karakterler bazen veritabanına giriyordu. Bu sorunu çözdü, çok teşekkür ederim.
giorgio79

@Jeff Günü - Bunun için teşekkürler. Cehaletimi bağışlayın, 'Katıya Ayarlamak' ne demek?
Ash501

[Jeff Day], mb_detect_order()bu parametre için varsayılan değer olmasına rağmen gönderiyor , çünkü katı kodlama algılamasını doğru olarak ayarlamak istiyordu (3. parametre) :)
jave.web

29

Anavatan Rusya'da 4 popüler kodlamamız var, bu yüzden sorunuz burada büyük talep görüyor.

Kod sayfaları kesiştiği için yalnızca karakter kodlarıyla kodlamayı algılayamazsınız. Farklı dillerdeki bazı kod sayfalarının tam kesişimleri bile vardır. Yani başka bir yaklaşıma ihtiyacımız var .

Bilinmeyen kodlamalarla çalışmanın tek yolu olasılıklarla çalışmaktır. Dolayısıyla, "bu metnin kodlaması nedir?" Sorusuna cevap vermek istemiyoruz, "bu metnin kodlaması en olası olan nedir ?" Anlamaya çalışıyoruz .

Buradaki popüler Rus teknoloji blogundaki bir adam bu yaklaşımı icat etti:

Desteklemek istediğiniz her kodlamada karakter kodlarının olasılık aralığını oluşturun. Kendi dilinizde bazı büyük metinler kullanarak inşa edebilirsiniz (örneğin, biraz kurgu, ingilizce için Shakespeare ve rusça için Tolstoy, lol). Böyle bir şey alacaksın:

    encoding_1:
    190 => 0.095249209893009,
    222 => 0.095249209893009,
    ...
    encoding_2:
    239 => 0.095249209893009,
    207 => 0.095249209893009,
    ...
    encoding_N:
    charcode => probabilty

Sonraki. Bilinmeyen kodlamadaki metni alırsınız ve "olasılık sözlüğünüzdeki" her kodlama için bilinmeyen kodlanmış metindeki her sembolün frekansını ararsınız. Sembollerin olasılıklarını toplayın. Daha büyük derecelendirmeye sahip kodlama muhtemelen kazanan olacaktır. Daha büyük metinler için daha iyi sonuçlar.

Eğer ilgilenirseniz , bu görevde size yardımcı olmaktan memnuniyet duyarım. İki karakter kodlu olasılık listesi oluşturarak doğruluğu büyük ölçüde artırabiliriz.

Btw. mb_detect_encoding certanly çalışmıyor. Evet, kesinlikle. Lütfen, "ext / mbstring / libmbfl / mbfl / mbfl_ident.c" içindeki mb_detect_encoding kaynak koduna bir göz atın.


11

Muhtemelen bunu denediniz ama neden sadece mb_convert_encoding işlevini kullanmıyorsunuz? Sağlanan metnin karakter kümesini otomatik olarak algılamaya çalışır veya bir listeyi iletebilirsiniz.

Ayrıca şunu çalıştırmayı denedim:

$text = "fiancée";
echo mb_convert_encoding($text, "UTF-8");
echo "<br/><br/>";
echo iconv(mb_detect_encoding($text), "UTF-8", $text);

ve sonuçlar her ikisi için de aynıdır. Metninizin 'nişanlı' olarak kısaltıldığını nasıl anlıyorsunuz? DB'de mi yoksa tarayıcıda mı?


Veritabanında, öyle görünüyor ki - kodunuzu yeni denedim ve kabul ediyorum.
Grim ...

1
Tabloda / sütunda tanımladığınız harmanlamanın da UTF-8 olduğundan emin olmak için kontrol edin.
Alexey Gerasimov

@AlexeyGerasimov Sanırım gerçekten araştırmam gerekiyor iconv. Neredeyse saf bir mb_ * yolu yapmayı denedim. Sen ne düşünüyorsun
Anthony Rutledge

5

Tamamen doğru olan bir dizenin karakter kümesini tanımlamanın bir yolu yoktur. Karakter setini tahmin etmenin yolları vardır. Bu yollardan biri ve muhtemelen / şu anda PHP'de en iyisi mb_detect_encoding () 'dir. Bu, dizinizi tarayacak ve belirli karakter kümelerine özgü olayları arayacaktır. Dizinize bağlı olarak, bu kadar ayırt edilebilir olaylar olmayabilir.

ISO-8859-1 karakter kümesine karşı ISO-8859-15'i ele alın ( http://en.wikipedia.org/wiki/ISO/IEC_8859-15#Changes_from_ISO-8859-1 )

Sadece bir avuç farklı karakter var ve daha da kötüsü, aynı baytlarla temsil ediliyorlar. Kodlamasını bilmeden bir dizge verildiğinde, 0xA4 baytının dizenizde ¤ veya € anlamına gelip gelmediğini tespit etmenin bir yolu yoktur, bu nedenle tam karakter kümesini bilmenin bir yolu yoktur.

(Not: Bir köprü gibi görünse de karakterin ¤ veya € olması gerekip gerekmediğini çevreleyen bağlama göre anlamaya çalışmak için bir insan faktörü veya daha gelişmiş bir tarama tekniği (örneğin Oroboros102'nin önerdiği gibi) ekleyebilirsiniz. çok uzak)

Örneğin UTF-8 ve ISO-8859-1 arasında daha belirgin farklılıklar vardır, bu yüzden hala emin olmadığınız zaman anlamaya çalışmanız gerekir, ancak bunun doğru olduğuna asla güvenemezsiniz ve asla güvenmemelisiniz.

İlginç okuma: http://kore-nordmann.de/blog/php_charset_encoding_FAQ.html#how-do-i-determine-the-charset-encoding-of-a-string

Doğru karakter setini sağlamanın başka yolları da var. Formlarla ilgili olarak, UTF-8'i olabildiğince zorlamaya çalışın (gönderiminizin her tarayıcıda UTF-8 olacağından emin olmak için kardan adama bakın: http://intertwingly.net/blog/2010/07/29/Rails-and -Karadam ) Bu yapılırsa, en azından formlarınız aracılığıyla gönderdiğiniz her metnin utf_8 olduğundan emin olabilirsiniz. Yüklenen dosyalarla ilgili olarak, algılamaya (belgenin BOM'unu kullanarak) yardımcı olmak için örn. Exec () (mümkünse sunucunuzda) aracılığıyla unix 'file -i' komutunu çalıştırmayı deneyin. Kazıma verileri ile ilgili olarak, HTTP başlıklarını okuyabilirsiniz, genellikle karakter kümesini belirtir. XML dosyalarını ayrıştırırken, XML meta verilerinin bir karakter kümesi tanımı içerip içermediğine bakın.

Karakter setini otomatik olarak tahmin etmeye çalışmak yerine, ilk olarak, mümkün olduğunda belirli bir karakter setini kendiniz sağlamaya çalışmalısınız veya tespit etmeye başvurmadan önce (varsa) onu aldığınız kaynaktan bir tanım almaya çalışmalısınız.


Şifrelenmiş veriler içeren formlar ve e-posta kayıt bağlantıları. Girdiğimi UTF-8 veya hiç yapmaya çalıştığım yer burası. Cevabım hakkında ne düşünüyorsun? Faydalı yorumlar takdir edilmektedir. Teşekkürler.
Anthony Rutledge

3

Burada gerçekten iyi cevaplar ve sorunuzu cevaplamaya yönelik girişimler var. Ben bir kodlama ustası değilim, ancak veritabanınıza kadar saf bir UTF-8 yığınına sahip olma arzunuzu anlıyorum . MySQL'in utf8mb4tablolar, alanlar ve bağlantılar için kodlamasını kullanıyorum .

Durumum "Sadece dezenfektanlarımın, doğrulayıcılarımın, iş mantığımın ve hazır ifadelerimin, veriler HTML formlarından veya e-posta kayıt bağlantılarından geldiğinde UTF-8 ile ilgilenmesini istiyorum." Yani, basit yolumla, şu fikirle başladım:

  1. Kodlamayı algılamaya çalışın: $encodings = ['UTF-8', 'ISO-8859-1', 'ASCII'];
  2. Kodlama tespit edilemezse, throw new RuntimeException
  3. Girdi ise UTF-8devam edin.
  4. Aksi takdirde, eğer öyleyse ISO-8859-1veyaASCII

    a. UTF-8'e dönüştürme girişimi (bekleyin, bitmedi)

    b. Dönüştürülen değerin kodlamasını tespit edin

    c. Rapor edilen kodlama ve dönüştürülen değerin ikisi de ise UTF-8, devam edin.

    d. Başka,throw new RuntimeException

Soyut sınıfımdan Sanitizer

Dezenfektan

    private function isUTF8($encoding, $value)
    {
        return (($encoding === 'UTF-8') && (utf8_encode(utf8_decode($value)) === $value));
    }

    private function utf8tify(&$value)
    {
        $encodings = ['UTF-8', 'ISO-8859-1', 'ASCII'];

        mb_internal_encoding('UTF-8');
        mb_substitute_character(0xfffd); //REPLACEMENT CHARACTER
        mb_detect_order($encodings);

        $stringEncoding = mb_detect_encoding($value, $encodings, true);

        if (!$stringEncoding) {
            $value = null;
            throw new \RuntimeException("Unable to identify character encoding in sanitizer.");
        }

        if ($this->isUTF8($stringEncoding, $value)) {
            return;
        } else {
            $value = mb_convert_encoding($value, 'UTF-8', $stringEncoding);
            $stringEncoding = mb_detect_encoding($value, $encodings, true);

            if ($this->isUTF8($stringEncoding, $value)) {
                return;
            } else {
                $value = null;
                throw new \RuntimeException("Unable to convert character encoding from ISO-8859-1, or ASCII, to UTF-8 in Sanitizer.");
            }
        }

        return;
    }

Kodlama kaygılarını soyut Sanitizersınıfımdan ayırmam ve basitçe bir Encodernesneyi somut bir alt örneğe enjekte etmem gerektiğine dair bir argüman yapılabilir Sanitizer. Bununla birlikte, yaklaşımımla ilgili temel sorun, daha fazla bilgi olmadan, istemediğim kodlama türlerini basitçe reddetmemdir (ve PHP mb_ * işlevlerine güveniyorum). Daha fazla araştırma yapmadan, bunun bazı popülasyonlara zarar verip vermediğini (veya önemli bilgileri kaybediyorsam) bilemiyorum. Bu yüzden daha fazlasını öğrenmem gerekiyor. Bu makaleyi buldum.

Metinle çalışmak için her programcının kodlamalar ve karakter kümeleri hakkında kesinlikle ve olumlu olarak bilmesi gerekenler

Ayrıca, e-posta kayıt bağlantılarıma ( OpenSSLveya kullanılarak mcrypt) şifrelenmiş veriler eklendiğinde ne olur ? Bu, kod çözmeyi engelleyebilir mi? Peki ya Windows-1252? Güvenlik etkileri ne olacak? Kullanımı utf8_decode()ve utf8_encode()de Sanitizer::isUTF8şüpheli vardır.

İnsanlar PHP mb_ * işlevlerindeki eksikliklere dikkat çektiler. Araştırmak için hiç zaman ayırmadım iconv, ancak mb_ * işlevlerinden daha iyi çalışıyorsa bana bildirin.


Bunu buldum, stackoverflow.com/a/3521396/1429677 bu soruna mükemmel bir cevap, işte lib github.com/neitanod/forceutf8
Llewellyn

2

Benim için asıl sorun, herhangi bir dizenin kaynağının hangi kodlama olacağını bilmememdir - bir metin kutusundan olabilir (kullanmak yalnızca kullanıcıya gerçekten formu gönderdiyse yararlıdır) veya olabilir yüklenen bir metin dosyasından, bu nedenle girdi üzerinde gerçekten hiçbir kontrolüm yok.

Bunun bir problem olduğunu sanmıyorum. Bir uygulama, girdinin kaynağını bilir. Formdan geliyorsa, durumunuzda UTF-8 kodlamasını kullanın. Bu işe yarıyor. Sadece sağlanan verilerin doğru şekilde kodlandığını doğrulayın (doğrulama). Tüm veritabanlarının UTF-8'i tam kapsamlı olarak desteklemediğini unutmayın.

Bu bir dosyaysa, UTF-8 kodlu olarak veritabanına değil ikili biçimde kaydetmezsiniz. Dosyayı tekrar çıkardığınızda, ikili çıktıyı da kullanın, o zaman bu tamamen şeffaftır.

Fikriniz, ikili olduğu için bir kullanıcının dosyayı indirdikten sonra yine de söyleyebilmesi için kodlamayı söyleyebilmesi güzel.

Bu yüzden, itiraf etmeliyim ki, sorunuzda ortaya attığınız belirli bir konu görmüyorum. Ama belki probleminizin ne olduğu konusunda biraz daha ayrıntı ekleyebilirsiniz.


Cevabımı görür ve sorun olur mu? Yapıcı yorumlar takdir edilmektedir. Teşekkürler.
Anthony Rutledge

2

Görünüşe göre sorunuz oldukça cevaplanmış, ancak durumunuzu basitleştirebilecek bir yaklaşımım var:

Mysql'den dize verilerini döndürmeye çalışırken benzer bir sorun yaşadım, hatta hem veritabanını hem de php'yi utf-8 olarak biçimlendirilmiş dizeleri döndürmek için yapılandırdım. Hatayı almamın tek yolu onları veritabanından döndürmekti.

Sonunda, internette gezinirken bununla başa çıkmanın gerçekten kolay bir yolunu buldum:

Tüm bu tür dize verilerini mysql'inize farklı biçimlerde ve harmanlamalarda kaydedebileceğinizi göz önüne alarak, yapmanız gereken tek şey, doğrudan php bağlantı dosyanızda harmanlamayı utf-8 olarak ayarlamaktır, aşağıdaki gibi:

$connection = new mysqli($server, $user, $pass, $db);
$connection->set_charset("utf8");

Wich, önce verileri herhangi bir formatta veya harmanlamada kaydettiğiniz ve yalnızca php dosyanıza geri döndüğünüzde dönüştürdüğünüz anlamına gelir.

Umarım yardımcı olmuştur!


1

Hangi kodlamanın kullanıldığını tahmin etmeye çalışmak için bir dizi ölçüm ayarlayabilirsiniz. Yine mükemmel değil, ancak mb_detect_encoding () 'den bazı eksikleri yakalayabilir.


Evet, mb_detect_encoding()bayanlardan bahsetmişken, cevabımın Sahra'da yazın kartopu şansı olduğunu düşünüyor musunuz?
Anthony Rutledge

1

"Bunu konsola götürmek" istiyorsanız, tavsiye ederim enca. Oldukça basit olanın aksine, mb_detect_encoding"kodlamalarını belirlemek için ayrıştırma, istatistiksel analiz, tahmin ve kara büyü karışımı" kullanır (lol - man sayfasına bakın ). Ancak, bu tür ülkeye özgü kodlamaları algılamak istiyorsanız, genellikle girdi dosyasının dilini geçmeniz gerekir. (Bununla birlikte, mb_detect_encodingkodlamanın tespit edilebilir olması için geçirilen kodlamalar listesinde "doğru yerde" görünmesi gerektiğinden , esasen aynı gereksinime sahiptir.)

encaayrıca buraya geldi: Unix'te bir dosyanın kodlaması komut dosyası (lar) aracılığıyla nasıl bulunur



-2
public function convertToUtf8($text) {
    if(!$this->html)
        $this->html = cURL('http://'.$this->url, array('timeout' => 15));

    $html = $this->html;
    preg_match('/<meta.*?charset=(|\")(.*?)("|\")/i', $html, $matches);

    $charset = $matches[2];

    if($charset)
        return mb_convert_encoding($text, 'UTF-8', $charset);
    else
        return $text;
}

cURL varsayılan seçenekleri:

curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);

Bunun gibi bir şey denedim. Bana yardımcı oldu. Meta karakter kümesi bilgisinde bulunursa, dönüştürüyorum, aksi takdirde hiçbir şey yapmıyorum.


errr, lütfen fonksiyonunuzu kontrol edip değişkenleri düzeltebilir misiniz?
Martin

$ Url nedir? $ Html nedir?
Martin
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.