Deteksi penyandian dan buat semuanya UTF-8

304

Saya membacakan banyak teks dari berbagai umpan RSS dan memasukkannya ke dalam basis data saya.

Tentu saja, ada beberapa pengkodean karakter yang berbeda yang digunakan dalam umpan, misalnya UTF-8 dan ISO 8859-1.

Sayangnya, terkadang ada masalah dengan penyandian teks. Contoh:

  1. "Ss" di "Fußball" akan terlihat seperti ini di basis data saya: "Ÿ". Jika "Ÿ", itu ditampilkan dengan benar.

  2. Terkadang, "ß" di "Fußball" terlihat seperti ini di basis data saya: "ß". Maka itu ditampilkan salah, tentu saja.

  3. Dalam kasus lain, "ß" disimpan sebagai "ß" - jadi tanpa perubahan apa pun. Maka itu juga ditampilkan salah.

Apa yang bisa saya lakukan untuk menghindari kasus 2 dan 3?

Bagaimana saya bisa membuat semuanya pengkodean yang sama, sebaiknya UTF-8? Kapan saya harus menggunakan utf8_encode(), kapan saya harus menggunakan utf8_decode()(jelas apa efeknya tetapi kapan saya harus menggunakan fungsi?) Dan kapan saya tidak melakukan apa-apa dengan input?

Bagaimana cara membuat semuanya menjadi sama? Mungkin dengan fungsinya mb_detect_encoding()? Bisakah saya menulis fungsi untuk ini? Jadi masalah saya adalah:

  1. Bagaimana cara mengetahui pengkodean yang digunakan teks?
  2. Bagaimana cara mengonversinya menjadi UTF-8 - apa pun pengkodean lama itu?

Apakah fungsi seperti ini berfungsi?

function correct_encoding($text) {
    $current_encoding = mb_detect_encoding($text, 'auto');
    $text = iconv($current_encoding, 'UTF-8', $text);
    return $text;
}

Saya sudah mengujinya, tetapi tidak berhasil. Apakah ada yang salah?

gak
sumber
36
"The" ß "di" Fußball "akan terlihat seperti ini di basis data saya:" Ÿ ".". Tidak harus seperti ß. Pastikan collation dan koneksi Anda sudah diatur dengan benar. Kalau tidak, pengurutan dan pencarian akan rusak untuk Anda.
Rich Bradshaw
5
Basis data Anda tidak diatur dengan benar. Jika Anda ingin menyimpan konten Unicode, cukup konfigurasikan untuk itu. Jadi, alih-alih mencoba menyelesaikan masalah dalam kode PHP Anda, Anda harus memperbaiki database terlebih dahulu.
dolmen
2
USE: $ from = mb_detect_encoding ($ text); $ text = mb_convert_encoding ($ text, 'UTF-8', $ from);
Informate.it

Jawaban:

363

Jika Anda menerapkan utf8_encode()string UTF-8 yang sudah ada, itu akan mengembalikan output UTF-8 yang kacau.

Saya membuat fungsi yang mengatasi semua masalah ini. Itu disebut Encoding::toUTF8().

Anda tidak perlu tahu apa penyandian string Anda. Ini bisa berupa Latin1 ( ISO 8859-1) , Windows-1252 atau UTF-8, atau string dapat memiliki campuran dari mereka. Encoding::toUTF8()akan mengonversi semuanya menjadi UTF-8.

Saya melakukannya karena sebuah layanan memberi saya data yang semua kacau, mencampur UTF-8 dan Latin1 dalam string yang sama.

Pemakaian:

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

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

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

Unduh:

https://github.com/neitanod/forceutf8

Saya telah menyertakan fungsi lain Encoding::fixUFT8(), yang akan memperbaiki setiap string UTF-8 yang terlihat kacau.

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 menampilkan:

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

Saya telah mengubah fungsi ( forceUTF8) menjadi keluarga fungsi statis pada kelas yang disebut Encoding. Fungsi baru adalah Encoding::toUTF8().

Sebastián Grignoli
sumber
1
Nah, jika Anda melihat kode, fixUTF8 cukup memanggil forceUTF8 sekali dan lagi sampai string dikembalikan tidak berubah. Satu panggilan ke fixUTF8 () membutuhkan setidaknya dua kali waktu panggilan untuk memaksaUTF8 (), jadi jauh lebih sedikit performanya. Saya membuat fixUTF8 () hanya untuk membuat program command line yang akan memperbaiki file "encode-rusak", tetapi dalam lingkungan hidup jarang diperlukan.
Sebastián Grignoli
3
Bagaimana ini mengkonversi karakter non-UTF8 ke UTF8, tanpa mengetahui pengkodean karakter yang tidak valid untuk memulai?
philfreo
4
Diasumsikan ISO-8859-1, jawabannya sudah mengatakan ini. Satu-satunya perbedaan antara forceUTF8 () dan utf8_encode () adalah forceUTF8 () mengenali karakter UTF8 dan membuatnya tidak berubah.
Sebastián Grignoli
28
"Kamu tidak perlu tahu apa penyandian stringmu." - Saya sangat tidak setuju. Menebak dan mencoba mungkin berhasil, tetapi Anda akan cepat atau lambat menemukan kasus tepi di mana tidak.
tipuan
4
Saya sangat setuju. Sebenarnya, saya tidak bermaksud menyatakan bahwa sebagai aturan umum, cukup jelaskan bahwa kelas ini mungkin membantu Anda jika itulah situasi yang Anda
alami
74

Anda harus terlebih dahulu mendeteksi encoding apa yang telah digunakan. Saat Anda mem-parsing umpan RSS (mungkin melalui HTTP), Anda harus membaca pengkodean dari charsetparameter Content-Typebidang header HTTP . Jika tidak ada, baca pengodean dari encodingatribut instruksi pemrosesan XML . Jika itu juga hilang, gunakan UTF-8 seperti yang ditentukan dalam spesifikasi .


Sunting    Inilah yang mungkin akan saya lakukan:

Saya akan menggunakan cURL untuk mengirim dan mengambil respons. Itu memungkinkan Anda untuk mengatur bidang tajuk spesifik dan mengambil tajuk respons juga. Setelah mengambil respons, Anda harus menguraikan respons HTTP dan membaginya menjadi header dan badan. Header kemudian harus berisi Content-Typebidang header yang berisi tipe MIME dan (mudah-mudahan) charsetparameter dengan pengkodean / charset juga. Jika tidak, kami akan menganalisis PI XML untuk keberadaan encodingatribut dan mendapatkan pengkodean dari sana. Jika itu juga tidak ada, spesifikasi XML menentukan untuk menggunakan UTF-8 sebagai pengkodean.

$url = 'http://www.lr-online.de/storage/rss/rss/sport.xml';

$accept = array(
    'type' => array('application/rss+xml', 'application/xml', 'application/rdf+xml', 'text/xml'),
    'charset' => array_diff(mb_list_encodings(), array('pass', 'auto', 'wchar', 'byte2be', 'byte2le', 'byte4be', 'byte4le', 'BASE64', 'UUENCODE', 'HTML-ENTITIES', 'Quoted-Printable', '7bit', '8bit'))
);
$header = array(
    'Accept: '.implode(', ', $accept['type']),
    'Accept-Charset: '.implode(', ', $accept['charset']),
);
$encoding = null;
$curl = curl_init($url);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_HEADER, true);
curl_setopt($curl, CURLOPT_HTTPHEADER, $header);
$response = curl_exec($curl);
if (!$response) {
    // error fetching the response
} else {
    $offset = strpos($response, "\r\n\r\n");
    $header = substr($response, 0, $offset);
    if (!$header || !preg_match('/^Content-Type:\s+([^;]+)(?:;\s*charset=(.*))?/im', $header, $match)) {
        // error parsing the response
    } else {
        if (!in_array(strtolower($match[1]), array_map('strtolower', $accept['type']))) {
            // type not accepted
        }
        $encoding = trim($match[2], '"\'');
    }
    if (!$encoding) {
        $body = substr($response, $offset + 4);
        if (preg_match('/^<\?xml\s+version=(?:"[^"]*"|\'[^\']*\')\s+encoding=("[^"]*"|\'[^\']*\')/s', $body, $match)) {
            $encoding = trim($match[1], '"\'');
        }
    }
    if (!$encoding) {
        $encoding = 'utf-8';
    } else {
        if (!in_array($encoding, array_map('strtolower', $accept['charset']))) {
            // encoding not accepted
        }
        if ($encoding != 'utf-8') {
            $body = mb_convert_encoding($body, 'utf-8', $encoding);
        }
    }
    $simpleXML = simplexml_load_string($body, null, LIBXML_NOERROR);
    if (!$simpleXML) {
        // parse error
    } else {
        echo $simpleXML->asXML();
    }
}
Gumbo
sumber
Terima kasih. Ini akan mudah. Tetapi apakah itu akan berhasil? Sering ada penyandian yang salah diberikan dalam header HTTP atau dalam atribut XML.
gak
25
Lagi: Itu bukan masalahmu. Standar ditetapkan untuk menghindari masalah seperti itu. Jika orang lain tidak mengikuti mereka, itu masalah mereka, bukan milikmu.
Gumbo
Ok, saya pikir Anda akhirnya meyakinkan saya sekarang. :)
gak
Terima kasih untuk kodenya. Tapi mengapa tidak menggunakan ini saja? paste.bradleygill.com/index.php?paste_id=9651 Kode Anda jauh lebih kompleks, apa yang lebih baik dengan itu?
gak
Pertama, Anda membuat dua permintaan, satu untuk header HTTP dan satu untuk data. Kedua, Anda sedang mencari setiap penampilan charset=dan encoding=dan tidak hanya pada posisi yang tepat. Dan ketiga, Anda tidak memeriksa apakah pengkodean yang dinyatakan diterima.
Gumbo
39

Mendeteksi encoding itu sulit.

mb_detect_encodingbekerja dengan menebak, berdasarkan sejumlah kandidat yang Anda lewati. Dalam beberapa pengkodean, urutan byte tertentu tidak valid, dan karenanya dapat membedakan berbagai kandidat. Sayangnya, ada banyak pengkodean, di mana byte yang sama valid (tetapi berbeda). Dalam kasus ini, tidak ada cara untuk menentukan pengkodean; Anda dapat menerapkan logika Anda sendiri untuk membuat tebakan dalam kasus-kasus ini. Misalnya, data yang berasal dari situs Jepang mungkin lebih cenderung memiliki penyandian Jepang.

Selama Anda hanya berurusan dengan bahasa Eropa Barat, tiga penyandian utama untuk dipertimbangkan adalah utf-8, iso-8859-1dan cp-1252. Karena ini adalah default untuk banyak platform, mereka juga kemungkinan besar dilaporkan salah. Misalnya. jika orang menggunakan pengkodean yang berbeda, mereka cenderung jujur ​​tentang hal itu, karena perangkat lunak mereka akan sangat sering rusak. Oleh karena itu, strategi yang baik adalah mempercayai penyedia, kecuali jika pengkodean dilaporkan sebagai salah satu dari ketiganya. Anda masih harus memeriksa dua kali bahwa itu memang benar, menggunakan mb_check_encoding(perhatikan bahwa valid tidak sama dengan menjadi - input yang sama mungkin berlaku untuk banyak pengkodean). Jika itu salah satunya, Anda bisa menggunakannyamb_detect_encodinguntuk membedakan di antara mereka. Untungnya itu cukup deterministik; Anda hanya perlu menggunakan urutan deteksi yang tepat, yaitu UTF-8,ISO-8859-1,WINDOWS-1252.

Setelah Anda mendeteksi pengkodean, Anda perlu mengubahnya ke representasi internal Anda ( UTF-8adalah satu-satunya pilihan yang waras). Fungsi utf8_encodeberubah ISO-8859-1menjadi UTF-8, jadi hanya bisa digunakan untuk tipe input tertentu. Untuk penyandian lainnya, gunakan mb_convert_encoding.

troelskn
sumber
Terima kasih banyak! Apa yang lebih baik: mb-convert-encoding () atau iconv ()? Saya tidak tahu apa perbedaannya. Ya, saya hanya perlu menguraikan bahasa Eropa Barat, terutama bahasa Inggris, Jerman dan Prancis.
gak
7
Saya baru saja melihat: mb-detect-encoding () tidak berguna. Ini hanya mendukung UTF-8, UTF-7, ASCII, EUC-JP, SJIS, eucJP-win, SJIS-win, JIS dan ISO-2022-JP. Yang paling penting bagi saya, ISO-8859-1 dan WINDOWS-1252, tidak didukung. Jadi saya tidak bisa menggunakan mb-detect-encoding ().
gak
1
Saya benar Sudah lama sejak saya menggunakannya. Anda harus menulis kode deteksi sendiri atau menggunakan utilitas eksternal. UTF-8 dapat ditentukan dengan cukup andal, karena urutan pelariannya cukup karakteristik. wp-1252 dan iso-8859-1 dapat dibedakan karena wp-1252 dapat berisi byte yang ilegal di iso-8859-1. Gunakan Wikipedia untuk mendapatkan detailnya, atau lihat di bagian komentar di php.net, di bawah berbagai fungsi terkait charset.
troelskn
Saya pikir Anda dapat membedakan pengkodean yang berbeda ketika Anda melihat bentuk-bentuk di mana nyanyian khusus muncul: "ß" Jerman muncul dalam bentuk yang berbeda: Kadang-kadang "Ÿ", kadang-kadang "ß" dan kadang-kadang "ß". Mengapa?
gak
Ya, tetapi kemudian Anda perlu mengetahui isi string sebelum membandingkannya, dan jenis itu mengalahkan tujuan di tempat pertama. German ß muncul secara berbeda karena memiliki nilai yang berbeda dalam penyandian yang berbeda. Beberapa karakter kebetulan terwakili dengan cara yang sama dalam pengkodean yang berbeda (mis. Semua karakter dalam rangkaian ascii dikodekan dengan cara yang sama di utf-8, iso-8859- * dan wp-1252), selama Anda menggunakan hanya karakter-karakter itu, semuanya terlihat sama. Itu sebabnya mereka beberapa kali disebut ascii-compatible.
troelskn
14

Cara yang sangat bagus untuk mengimplementasikan isUTF8-fungsi dapat ditemukan di php.net :

function isUTF8($string) {
    return (utf8_encode(utf8_decode($string)) == $string);
}
kecapi
sumber
16
Sayangnya, ini hanya berfungsi ketika string hanya terdiri dari karakter yang termasuk dalam ISO-8859-1. Tetapi ini bisa berhasil: @iconv ('utf-8', 'utf-8 // IGNORE', $ str) == $ str
Christian Davén
@Christian: Memang, itulah yang direkomendasikan oleh penulis High Performance MySQL juga.
Alix Axel
1
Ini tidak berfungsi dengan benar: echo (int) isUTF8 ('z'); # 1 echo (int) isUTF8 (NULL); # 1
Yousha Aleayoub
1
Meskipun tidak sempurna, saya pikir ini adalah cara yang bagus untuk mengimplementasikan pemeriksaan UTF-8 yang samar.
Mateng
1
mb_check_encoding($string, 'UTF-8')
deceze
13

Cheatsheet ini berisi daftar beberapa peringatan umum terkait penanganan UTF-8 di PHP: http://developer.loftdigital.com/blog/php-utf-8-cheatsheet

Fungsi ini mendeteksi karakter multibyte dalam sebuah string mungkin juga bermanfaat ( sumber ):


function detectUTF8($string)
{
    return preg_match('%(?:
        [\xC2-\xDF][\x80-\xBF]             # non-overlong 2-byte
        |\xE0[\xA0-\xBF][\x80-\xBF]        # excluding overlongs
        |[\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} # straight 3-byte
        |\xED[\x80-\x9F][\x80-\xBF]        # excluding surrogates
        |\xF0[\x90-\xBF][\x80-\xBF]{2}     # planes 1-3
        |[\xF1-\xF3][\x80-\xBF]{3}         # planes 4-15
        |\xF4[\x80-\x8F][\x80-\xBF]{2}     # plane 16
        )+%xs', 
    $string);
}

miek
sumber
2
Saya pikir itu tidak berfungsi dengan benar: echo detectUTF8 ('3٣3'); # 1
Yousha Aleayoub
10

Sedikit kepala. Anda mengatakan bahwa "ß" harus ditampilkan sebagai "Ÿ" di database Anda.

Ini mungkin karena Anda menggunakan database dengan pengkodean karakter Latin-1 atau mungkin koneksi PHP-MySQL Anda salah, ini, P, percaya MySQL Anda diatur untuk menggunakan UTF-8, sehingga mengirimkan data sebagai UTF-8 , tetapi MySQL Anda yakin PHP mengirim data yang disandikan sebagai ISO 8859-1, jadi mungkin sekali lagi mencoba untuk menyandikan data terkirim Anda sebagai UTF-8, yang menyebabkan masalah seperti ini.

Lihatlah mysql_set_charset . Ini dapat membantu Anda.

Krynble
sumber
4

Pengkodean Anda sepertinya dikodekan ke dalam UTF-8 dua kali ; yaitu, dari beberapa pengkodean lainnya, ke UTF-8, dan lagi ke UTF-8. Seolah-olah Anda memiliki ISO 8859-1, dikonversi dari ISO 8859-1 ke UTF-8, dan memperlakukan string baru sebagai ISO 8859-1 untuk konversi lain menjadi UTF-8.

Inilah beberapa kodesemu dari apa yang Anda lakukan:

$inputstring = getFromUser();
$utf8string = iconv($current_encoding, 'utf-8', $inputstring);
$flawedstring = iconv($current_encoding, 'utf-8', $utf8string);

Kamu harus mencobanya:

  1. mendeteksi encoding menggunakan mb_detect_encoding()atau apa pun yang Anda suka gunakan
  2. jika itu UTF-8, konversikan ke ISO 8859-1, dan ulangi langkah 1
  3. akhirnya, konversikan kembali ke UTF-8

Itu mengasumsikan bahwa dalam konversi "tengah" Anda menggunakan ISO 8859-1. Jika Anda menggunakan Windows-1252, kemudian konversikan ke Windows-1252 (latin1). Pengkodean sumber asli tidak penting; yang Anda gunakan dalam cacat, konversi kedua adalah.

Ini dugaan saya tentang apa yang terjadi; ada sangat sedikit lagi yang bisa Anda lakukan untuk mendapatkan empat byte sebagai pengganti satu byte ASCII yang diperluas.

Bahasa Jerman juga menggunakan ISO 8859-2 dan Windows-1250 (Latin-2).

Ivan Vučica
sumber
3

Hal yang menarik tentang mb_detect_encodingdan mb_convert_encodingadalah bahwa urutan pengkodean yang Anda sarankan itu penting:

// $input is actually UTF-8

mb_detect_encoding($input, "UTF-8", "ISO-8859-9, UTF-8");
// ISO-8859-9 (WRONG!)

mb_detect_encoding($input, "UTF-8", "UTF-8, ISO-8859-9");
// UTF-8 (OK)

Jadi, Anda mungkin ingin menggunakan perintah tertentu saat menentukan pengkodean yang diharapkan. Namun, perlu diingat bahwa ini tidak mudah.

Halil Özgür
sumber
2
Ini terjadi karena ISO-8859-9 dalam praktiknya akan menerima input biner apa pun. Hal yang sama berlaku untuk Windows-1252 dan teman-teman. Anda harus terlebih dahulu menguji penyandian yang gagal menerima input.
Mikko Rantalainen
@MikkoRantalainen, ya, saya kira ini bagian dari dokumen mengatakan sesuatu yang serupa: php.net/manual/en/function.mb-detect-order.php#example-2985
Halil Özgür
Mempertimbangkan bahwa spesifikasi WHATWG HTML mendefinisikan Windows 1252 sebagai penyandian default, seharusnya cukup aman untuk menganggapnya if ($input_is_not_UTF8) $input_is_windows1252 = true;. Lihat juga: html.spec.whatwg.org/multipage/…
Mikko Rantalainen
3

Anda perlu menguji set karakter pada input karena tanggapan dapat dikodekan dengan pengkodean yang berbeda.

Saya memaksa semua konten dikirim ke UTF-8 dengan melakukan deteksi dan terjemahan menggunakan fungsi berikut:

function fixRequestCharset()
{
  $ref = array(&$_GET, &$_POST, &$_REQUEST);
  foreach ($ref as &$var)
  {
    foreach ($var as $key => $val)
    {
      $encoding = mb_detect_encoding($var[$key], mb_detect_order(), true);
      if (!$encoding)
        continue;
      if (strcasecmp($encoding, 'UTF-8') != 0)
      {
        $encoding = iconv($encoding, 'UTF-8', $var[$key]);
        if ($encoding === false)
          continue;
        $var[$key] = $encoding;
      }
    }
  }
}

Rutin itu akan mengubah semua variabel PHP yang berasal dari remote host menjadi UTF-8.

Atau abaikan nilainya jika pengkodean tidak dapat dideteksi atau dikonversi.

Anda dapat menyesuaikannya dengan kebutuhan Anda.

Hanya memohonnya sebelum menggunakan variabel.

cavila
sumber
apa tujuan menggunakan mb_detect_order () tanpa daftar penyandian yang disahkan?
giorgio79
Tujuannya adalah untuk mengembalikan sistem yang dikonfigurasi susunan pengodean terurut yang didefinisikan dalam php.ini yang digunakan. Ini diperlukan oleh mb_detect_encoding untuk mengisi parameter ketiga.
cavila
2

Mengerjakan pengkodean karakter dari umpan RSS tampaknya rumit . Bahkan halaman web biasa sering menghilangkan, atau berbohong tentang, pengkodeannya.

Jadi Anda bisa mencoba menggunakan cara yang benar untuk mendeteksi pengkodean dan kemudian kembali ke beberapa bentuk deteksi otomatis (menebak).

Kevin ORourke
sumber
Saya tidak ingin membaca pengodean dari informasi umpan. Jadi sama jika informasi umpan salah. Saya ingin mendeteksi pengodean dari teks.
gak
@ marco92w: Bukan masalah Anda jika pengkodean yang dinyatakan salah. Standar belum ditetapkan untuk bersenang-senang.
Gumbo
1
@ Gumbo: tetapi jika Anda bekerja di dunia nyata, Anda harus dapat menangani hal-hal seperti penyandian yang dinyatakan salah. Masalahnya adalah sangat sulit untuk menebak (dengan benar) pengkodean hanya dari beberapa teks. Standar sangat bagus, tetapi banyak (sebagian besar?) Dari halaman / feed di luar sana tidak mematuhinya.
Kevin ORourke
@Kevin ORourke: Tepat, benar. Itu masalah saya. @ Gumbo: Ya, ini masalah saya. Saya ingin membaca feed dan mengumpulkannya. Jadi saya harus memperbaiki pengkodean yang salah.
gak
@ marco92w: Tetapi Anda tidak dapat memperbaiki pengkodean jika Anda tidak tahu pengkodean yang benar dan pengkodean saat ini. Dan itulah yang charset/ encodingdeklarasi jika untuk: menggambarkan pengkodean data dikodekan.
Gumbo
2

Saya tahu ini adalah pertanyaan yang lebih tua, tetapi saya pikir jawaban yang bermanfaat tidak pernah sakit. Saya mengalami masalah dengan pengkodean antara aplikasi desktop, SQLite, dan variabel GET / POST. Beberapa di UTF-8, beberapa di ASCII, dan pada dasarnya semuanya akan kacau ketika karakter asing terlibat.

Ini solusinya. Itu menggosok GET / POST / REQUEST Anda (saya hilangkan cookie, tetapi Anda bisa menambahkannya jika diinginkan) pada setiap halaman dimuat sebelum diproses. Ini berfungsi dengan baik di header. PHP akan memberikan peringatan jika tidak dapat mendeteksi pengkodean sumber secara otomatis, sehingga peringatan ini ditekan dengan @ 's.

//Convert everything in our vars to UTF-8 for playing nice with the database...
//Use some auto detection here to help us not double-encode...
//Suppress possible warnings with @'s for when encoding cannot be detected
try
{
    $process = array(&$_GET, &$_POST, &$_REQUEST);
    while (list($key, $val) = each($process)) {
        foreach ($val as $k => $v) {
            unset($process[$key][$k]);
            if (is_array($v)) {
                $process[$key][@mb_convert_encoding($k,'UTF-8','auto')] = $v;
                $process[] = &$process[$key][@mb_convert_encoding($k,'UTF-8','auto')];
            } else {
                $process[$key][@mb_convert_encoding($k,'UTF-8','auto')] = @mb_convert_encoding($v,'UTF-8','auto');
            }
        }
    }
    unset($process);
}
catch(Exception $ex){}
jocull
sumber
Terima kasih atas jawabannya, jocull. Fungsi mb_convert_encoding () adalah apa yang sudah kita miliki di sini, kan? ;) Jadi satu-satunya hal baru dalam jawaban Anda adalah loop untuk mengubah pengkodean di semua variabel.
gak
2

Saya memeriksa solusi untuk penyandian sejak lama , dan halaman ini mungkin merupakan kesimpulan dari pencarian bertahun-tahun! Saya menguji beberapa saran yang Anda sebutkan dan inilah catatan saya:

Ini adalah string pengujian saya:

ini adalah string "wrìng wrìtten" tetapi saya harus pás chòrs khusus untuk melihat mereka, konversikan dengan f !!nctìon !! & itu dia!

Saya melakukan INSERT untuk menyimpan string ini pada database di bidang yang ditetapkan sebagai utf8_general_ci

Set karakter halaman saya adalah UTF-8.

Jika saya melakukan INSERT seperti itu, di basis data saya, saya mungkin memiliki beberapa karakter yang berasal dari Mars ...

Jadi saya perlu mengubahnya menjadi "waras" UTF-8. Saya mencoba utf8_encode(), tetapi masih ada alien yang menyerang basis data saya ...

Jadi saya mencoba menggunakan fungsi yang forceUTF8diposting pada nomor 8, tetapi dalam database string yang disimpan terlihat seperti ini:

ini adalah "wròng wrìtten" string tetapi saya tidak perlu pù 'sòme' chs rs khusus untuk melihat tha ¨ m, convert ¨ d oleh fÃÃnctìon !! & itu dia!

Jadi mengumpulkan beberapa informasi lebih lanjut di halaman ini dan menggabungkannya dengan informasi lain di halaman lain, saya memecahkan masalah saya dengan solusi ini:

$finallyIDidIt = mb_convert_encoding(
  $string,
  mysql_client_encoding($resourceID),
  mb_detect_encoding($string)
);

Sekarang dalam database saya, saya memiliki string dengan pengkodean yang benar.

CATATAN: Hanya catatan yang harus diperhatikan yang berfungsi mysql_client_encoding! Anda harus terhubung ke database, karena fungsi ini menginginkan ID sumber daya sebagai parameter.

Tapi yah, saya hanya melakukan pengkodean ulang sebelum INSERT saya jadi bagi saya itu bukan masalah.

Mauro
sumber
1
Mengapa Anda tidak menggunakan UTF-8pengkodean klien untuk mysql? Tidak perlu konversi manual dengan cara ini
Esailija
2

Itu sederhana: ketika Anda mendapatkan sesuatu yang tidak UTF-8, Anda harus menyandikan bahwa dalam UTF-8.

Jadi, saat Anda mengambil feed tertentu, ISO 8859-1 menguraikannya utf8_encode.

Namun, jika Anda mengambil umpan UTF-8, Anda tidak perlu melakukan apa pun.

Seb
sumber
Terima kasih! OK, saya bisa mencari tahu bagaimana feed dikodekan dengan menggunakan mb-detect-encoding (), kan? Tapi apa yang bisa saya lakukan jika feed adalah ASCII? utf8-encode () hanya untuk ISO-8859-1 ke UTF-8, bukan?
gak
ASCII adalah subset dari ISO-8859-1 DAN UTF-8, jadi menggunakan utf8-encode () seharusnya tidak membuat perubahan - JIKA itu sebenarnya hanya ASCII
Michael Borgwardt
Jadi saya selalu dapat menggunakan utf8_encode jika bukan UTF-8? Ini akan sangat mudah. Teks yang merupakan ASCII menurut mb-detect-encoding () berisi "& # 228;". Apakah ini karakter ASCII? Atau apakah itu HTML?
gak
Itu HTML. Sebenarnya itu dikodekan jadi ketika Anda mencetaknya di halaman yang diberikan itu menunjukkan ok. Jika mau, Anda dapat menggunakan ut8_encode () terlebih dahulu kemudian html_entity_decode ().
Seb
1
Karakter ß dikodekan dalam UTF-8 dengan urutan byte 0xC39F. Diterjemahkan dengan Windows-1252, urutan itu mewakili dua karakter  (0xC3) dan Ÿ (0x9F). Dan jika Anda menyandikan urutan byte ini lagi dengan UTF-8, Anda akan mendapatkan 0xC383 0xC29F yang mewakili ß pada Windows-1252. Jadi kesalahan Anda adalah menangani data yang disandikan UTF-8 ini sebagai sesuatu dengan penyandian selain UTF-8. Urutan byte ini disajikan sebagai karakter yang Anda lihat hanya masalah interpretasi. Jika Anda menggunakan pengkodean / rangkaian karakter lain, Anda mungkin akan melihat karakter lain.
Gumbo
1

php.net/mb_detect_encoding

echo mb_detect_encoding($str, "auto");

atau

echo mb_detect_encoding($str, "UTF-8, ASCII, ISO-8859-1");

saya benar-benar tidak tahu apa hasilnya, tetapi saya sarankan Anda hanya mengambil beberapa feed Anda dengan pengkodean yang berbeda dan coba apakah mb_detect_encodingberfungsi atau tidak.

pembaruan
otomatis adalah kependekan dari "ASCII, JIS, UTF-8, EUC-JP, SJIS". ia mengembalikan charset yang terdeteksi, yang dapat Anda gunakan untuk mengonversi string menjadi utf-8 dengan iconv .

<?php
function convertToUTF8($str) {
    $enc = mb_detect_encoding($str);

    if ($enc && $enc != 'UTF-8') {
        return iconv($enc, 'UTF-8', $str);
    } else {
        return $str;
    }
}
?>

saya belum mengujinya, jadi tidak ada jaminan. dan mungkin ada cara yang lebih sederhana.

STFs
sumber
Terima kasih. Apa perbedaan antara 'otomatis' dan 'UTF-8, ASCII, ISO-8859-1' sebagai argumen kedua? Apakah 'otomatis' menampilkan lebih banyak penyandian? Maka akan lebih baik menggunakan 'otomatis', bukan? Jika itu benar-benar berfungsi tanpa bug maka saya hanya harus mengubah "ASCII" atau "ISO-8859-1" menjadi "UTF-8". Bagaimana?
gak
2
Fungsi Anda tidak berfungsi dengan baik dalam semua kasus. Kadang-kadang saya mendapatkan kesalahan: Pemberitahuan: iconv (): Terdeteksi karakter ilegal dalam string input di ...
caw
1

@ Harpax yang bekerja untuk saya. Dalam kasus saya, ini cukup bagus:

if (isUTF8($str)) { 
    echo $str; 
}
else
{
    echo iconv("ISO-8859-1", "UTF-8//TRANSLIT", $str);
}
PJ Brunet
sumber
0

Setelah memilah skrip php Anda, jangan lupa memberi tahu mysql charset apa yang Anda lewati dan ingin menerima.

Contoh: set karakter set utf8

Melewati data utf8 ke tabel latin1 dalam sesi latin1 I / O memberikan mereka birdfeet jahat. Saya melihat ini setiap hari di toko-toko oscommerce. Kembali dan keempat sepertinya benar. Tapi phpmyadmin akan menunjukkan kebenaran. Dengan memberi tahu mysql charset apa yang Anda lewati, ia akan menangani konversi data mysql untuk Anda.

Bagaimana memulihkan data mysql teracak yang ada adalah utas lain untuk didiskusikan. :)

tim
sumber
0

Versi ini untuk bahasa Jerman tetapi Anda dapat memodifikasi $ CHARSETS dan $ TESTCHARS

class CharsetDetector
{
private static $CHARSETS = array(
"ISO_8859-1",
"ISO_8859-15",
"CP850"
);
private static $TESTCHARS = array(
"€",
"ä",
"Ä",
"ö",
"Ö",
"ü",
"Ü",
"ß"
);
public static function convert($string)
{
    return self::__iconv($string, self::getCharset($string));
}
public static function getCharset($string)
{
    $normalized = self::__normalize($string);
    if(!strlen($normalized))return "UTF-8";
    $best = "UTF-8";
    $charcountbest = 0;
    foreach (self::$CHARSETS as $charset) {
        $str = self::__iconv($normalized, $charset);
        $charcount = 0;
        $stop   = mb_strlen( $str, "UTF-8");

        for( $idx = 0; $idx < $stop; $idx++)
        {
            $char = mb_substr( $str, $idx, 1, "UTF-8");
            foreach (self::$TESTCHARS as $testchar) {

                if($char == $testchar)
                {

                    $charcount++;
                    break;
                }
            }
        }
        if($charcount>$charcountbest)
        {
            $charcountbest=$charcount;
            $best=$charset;
        }
        //echo $text."<br />";
    }
    return $best;
}
private static function __normalize($str)
{

$len = strlen($str);
$ret = "";
for($i = 0; $i < $len; $i++){
    $c = ord($str[$i]);
    if ($c > 128) {
        if (($c > 247)) $ret .=$str[$i];
        elseif ($c > 239) $bytes = 4;
        elseif ($c > 223) $bytes = 3;
        elseif ($c > 191) $bytes = 2;
        else $ret .=$str[$i];
        if (($i + $bytes) > $len) $ret .=$str[$i];
        $ret2=$str[$i];
        while ($bytes > 1) {
            $i++;
            $b = ord($str[$i]);
            if ($b < 128 || $b > 191) {$ret .=$ret2; $ret2=""; $i+=$bytes-1;$bytes=1; break;}
            else $ret2.=$str[$i];
            $bytes--;
        }
    }
}
return $ret; 
}
private static function __iconv($string, $charset)
{
    return iconv ( $charset, "UTF-8" , $string );
}
}

Lukas Gottschall
sumber
0

Dapatkan encoding dari header dan konversikan ke utf-8.

$post_url='http://website.domain';

/// Get headers ////////////////////////////////////////////////////////////
function get_headers_curl($url) 
{ 
    $ch = curl_init(); 

    curl_setopt($ch, CURLOPT_URL,            $url); 
    curl_setopt($ch, CURLOPT_HEADER,         true); 
    curl_setopt($ch, CURLOPT_NOBODY,         true); 
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 
    curl_setopt($ch, CURLOPT_TIMEOUT,        15); 

    $r = curl_exec($ch); 
    return $r; 
}
$the_header = get_headers_curl($post_url);
/// check for redirect /////////////////////////////////////////////////
if (preg_match("/Location:/i", $the_header)) {
    $arr = explode('Location:', $the_header);
    $location = $arr[1];

    $location=explode(chr(10), $location);
    $location = $location[0];

$the_header = get_headers_curl(trim($location));
}
/// Get charset /////////////////////////////////////////////////////////////////////
if (preg_match("/charset=/i", $the_header)) {
    $arr = explode('charset=', $the_header);
    $charset = $arr[1];

    $charset=explode(chr(10), $charset);
    $charset = $charset[0];
    }
///////////////////////////////////////////////////////////////////////////////
// echo $charset;

if($charset && $charset!='UTF-8') { $html = iconv($charset, "UTF-8", $html); }
Arsen
sumber
0

Ÿadalah untuk Mojibake ß. Di database Anda, Anda mungkin memiliki hex

DF if the column is "latin1",
C39F if the column is utf8 -- OR -- it is latin1, but "double-encoded"
C383C5B8 if double-encoded into a utf8 column

Anda tidak boleh menggunakan fungsi encoding / decoding dalam PHP; alih-alih, Anda harus mengatur basis data dan koneksi dengan benar.

Jika MySQL terlibat, lihat: Masalah dengan karakter utf8; apa yang saya lihat bukan apa yang saya simpan

Rick James
sumber
0

Saya menemukan solusi di sini http://deer.org.ua/2009/10/06/1/

class Encoding
{
    /**
     * http://deer.org.ua/2009/10/06/1/
     * @param $string
     * @return null
     */
    public static function detect_encoding($string)
    {
        static $list = ['utf-8', 'windows-1251'];

        foreach ($list as $item) {
            try {
                $sample = iconv($item, $item, $string);
            } catch (\Exception $e) {
                continue;
            }
            if (md5($sample) == md5($string)) {
                return $item;
            }
        }
        return null;
    }
}

$content = file_get_contents($file['tmp_name']);
$encoding = Encoding::detect_encoding($content);
if ($encoding != 'utf-8') {
    $result = iconv($encoding, 'utf-8', $content);
} else {
    $result = $content;
}

Saya pikir @ adalah keputusan yang buruk, dan membuat beberapa perubahan pada solusi dari deer.org.ua;

outdead
sumber
0

Jawaban yang paling banyak dipilih tidak berfungsi. Ini milik saya dan saya harap ini membantu.

function toUTF8($raw) {
    try{
        return mb_convert_encoding($raw, "UTF-8", "auto"); 
    }catch(\Exception $e){
        return mb_convert_encoding($raw, "UTF-8", "GBK"); 
    }
}
ch271828n
sumber
1
Apakah Anda memiliki wawasan mengapa, atau bagaimana file Anda berbeda? Bagian mana yang tidak bekerja untuk Anda? Misalnya: Huruf besar huruf Jerman tidak dikonversi dengan benar. Penasaran, apa itu "GBK"?
SherylHohman
-1

Ketika Anda mencoba menangani multi bahasa seperti Jepang dan Korea, Anda mungkin akan mendapat masalah. mb_convert_encoding dengan parameter 'otomatis' tidak berfungsi dengan baik. Pengaturan mb_detect_order ('ASCII, UTF-8, JIS, EUC-JP, SJIS, EUC-KR, UHC') tidak membantu karena akan mendeteksi EUC- * salah.

Saya menyimpulkan bahwa selama string input berasal dari HTML, harus menggunakan 'charset' dalam elemen meta. Saya menggunakan Parser DOM HTML Sederhana karena mendukung HTML yang tidak valid.

Cuplikan di bawah ini mengekstrak elemen judul dari halaman web. Jika Anda ingin mengonversi seluruh halaman, maka Anda mungkin ingin menghapus beberapa baris.

<?php
require_once 'simple_html_dom.php';

echo convert_title_to_utf8(file_get_contents($argv[1])), PHP_EOL;

function convert_title_to_utf8($contents)
{
    $dom = str_get_html($contents);
    $title = $dom->find('title', 0);
    if (empty($title)) {
        return null;
    }
    $title = $title->plaintext;
    $metas = $dom->find('meta');
    $charset = 'auto';
    foreach ($metas as $meta) {
        if (!empty($meta->charset)) { // html5
            $charset = $meta->charset;
        } else if (preg_match('@charset=(.+)@', $meta->content, $match)) {
            $charset = $match[1];
        }
    }
    if (!in_array(strtolower($charset), array_map('strtolower', mb_list_encodings()))) {
        $charset = 'auto';
    }
    return mb_convert_encoding($title, 'UTF-8', $charset);
}
Nobu
sumber
-1

Saya memiliki masalah yang sama dengan phpQuery ( ISO-8859-1 bukannya UTF-8 ) dan peretasan ini membantu saya:

$html = '<?xml version="1.0" encoding="UTF-8" ?>' . $html;

mb_internal_encoding('UTF-8'), phpQuery::newDocumentHTML($html, 'utf-8'), mbstring.internal_encodingDan manipulasi lainnya tidak mengambil efek apapun.

pengguna2448995
sumber
-1

Coba tanpa 'otomatis'

Itu adalah:

mb_detect_encoding($text)

dari pada:

mb_detect_encoding($text, 'auto')

Informasi lebih lanjut dapat ditemukan di sini: mb_detect_encoding

tkartas
sumber