Cara andal menebak encoding antara MacRoman, CP1252, Latin1, UTF-8, dan ASCII

99

Di tempat kerja sepertinya tidak ada minggu yang berlalu tanpa beberapa konipsi terkait pengkodean, bencana, atau malapetaka. Masalahnya biasanya berasal dari programmer yang mengira mereka dapat memproses file "teks" dengan andal tanpa menentukan encodingnya. Tapi Anda tidak bisa.

Jadi diputuskan untuk sejak saat itu melarang file memiliki nama yang berakhiran *.txtatau *.text. Pemikirannya adalah bahwa ekstensi tersebut menyesatkan programmer kasual menjadi kepuasan yang tumpul tentang pengkodean, dan ini mengarah pada penanganan yang tidak tepat. Hampir lebih baik tidak memiliki ekstensi sama sekali, karena setidaknya Anda tahu bahwa Anda tidak tahu apa yang Anda miliki.

Namun, kami tidak akan pergi sejauh itu. Sebagai gantinya, Anda diharapkan menggunakan nama file yang diakhiri dengan encoding. Jadi untuk file teks, misalnya, ini akan menjadi sesuatu seperti README.ascii, README.latin1, README.utf8, dll

Untuk file yang membutuhkan ekstensi tertentu, jika seseorang dapat menentukan pengkodean di dalam file itu sendiri, seperti di Perl atau Python, maka Anda harus melakukannya. Untuk file seperti sumber Java yang tidak memiliki fasilitas internal untuk file tersebut, Anda harus meletakkan pengkodean sebelum ekstensi, seperti SomeClass-utf8.java.

Untuk keluaran, UTF-8 sangat disukai.

Tetapi sebagai masukan, kita perlu mencari cara untuk menangani ribuan file dalam basis kode kita yang diberi nama *.txt. Kami ingin mengubah nama semuanya agar sesuai dengan standar baru kami. Tapi kita tidak mungkin melihat semuanya. Jadi kita membutuhkan perpustakaan atau program yang benar-benar berfungsi.

Ini beragam dalam ASCII, ISO-8859-1, UTF-8, Microsoft CP1252, atau Apple MacRoman. Meskipun kami tahu kami dapat mengetahui apakah sesuatu itu ASCII, dan kami memiliki perubahan yang baik dalam mengetahui apakah sesuatu mungkin UTF-8, kami bingung tentang pengkodean 8-bit. Karena kami berjalan di lingkungan Unix campuran (Solaris, Linux, Darwin) dengan sebagian besar desktop adalah Mac, kami memiliki beberapa file MacRoman yang mengganggu. Dan ini khususnya merupakan masalah.

Untuk beberapa waktu sekarang saya telah mencari cara untuk menentukan secara terprogram dari

  1. ASCII
  2. ISO-8859-1
  3. CP1252
  4. MacRoman
  5. UTF-8

ada file di dalamnya, dan saya belum menemukan program atau pustaka yang dapat diandalkan untuk membedakan antara ketiga penyandiaksaraan 8-bit yang berbeda. Kami mungkin memiliki lebih dari seribu file MacRoman saja, jadi detektor charset apa pun yang kami gunakan harus dapat mengendusnya. Tidak ada yang bisa saya lihat yang bisa mengatur triknya. Saya memiliki harapan besar untuk perpustakaan detektor charset ICU , tetapi tidak dapat menangani MacRoman. Saya juga telah melihat modul untuk melakukan hal yang sama di Perl dan Python, tetapi lagi dan lagi ceritanya selalu sama: tidak ada dukungan untuk mendeteksi MacRoman.

Oleh karena itu, yang saya cari adalah pustaka atau program yang ada yang andal menentukan mana dari lima penyandiaksaraan file itu — dan sebaiknya lebih dari itu. Secara khusus itu harus membedakan antara tiga pengkodean 3-bit yang telah saya kutip, terutama MacRoman . File-file tersebut lebih dari 99% teks bahasa Inggris; ada beberapa dalam bahasa lain, tetapi tidak banyak.

Jika itu adalah kode pustaka, preferensi bahasa kami adalah Perl, C, Java, atau Python, dan dalam urutan itu. Jika ini hanya sebuah program, maka kami tidak terlalu peduli dengan bahasanya asalkan datang dalam sumber penuh, berjalan di Unix, dan sepenuhnya tidak terbebani.

Apakah ada orang lain yang memiliki masalah jutaan file teks lama yang dikodekan secara acak? Jika ya, bagaimana Anda mencoba mengatasinya, dan seberapa sukses Anda? Ini adalah aspek terpenting dari pertanyaan saya, tetapi saya juga tertarik pada apakah menurut Anda mendorong pemrogram untuk memberi nama (atau mengganti nama) file mereka dengan penyandian sebenarnya untuk file tersebut akan membantu kami menghindari masalah di masa mendatang. Adakah yang pernah mencoba untuk menegakkan ini secara kelembagaan, dan jika ya, apakah itu berhasil atau tidak, dan mengapa?

Dan ya, saya sangat memahami mengapa seseorang tidak dapat menjamin jawaban yang pasti mengingat sifat masalahnya. Ini terutama terjadi pada file kecil, di mana Anda tidak memiliki cukup data untuk melanjutkan. Untungnya, file kami jarang berukuran kecil. Selain READMEfile acak , sebagian besar berukuran antara 50k hingga 250k, dan banyak yang lebih besar. Apa pun yang berukuran lebih dari beberapa K dijamin akan menggunakan bahasa Inggris.

Domain masalahnya adalah penambangan teks biomedis, jadi terkadang kita berurusan dengan korpora yang luas dan sangat besar, seperti semua penyimpanan Akses Terbuka PubMedCentral. File yang agak besar adalah BioThesaurus 6.0, dengan ukuran 5,7 gigabyte. File ini sangat mengganggu karena hampir semuanya UTF-8. Namun, beberapa numbskull pergi dan terjebak beberapa baris di dalamnya yang ada dalam beberapa pengkodean 8-bit — Microsoft CP1252, saya yakin. Butuh waktu cukup lama sebelum Anda melakukan perjalanan yang satu itu. :(

tchrist
sumber
Lihat stackoverflow.com/questions/4255305/… untuk solusinya
mpenkov

Jawaban:

86

Pertama, kasus mudahnya:

ASCII

Jika data Anda tidak berisi byte di atas 0x7F, maka itu ASCII. (Atau pengkodean ISO646 7-bit, tetapi itu sangat usang.)

UTF-8

Jika memvalidasi data Anda sebagai UTF-8, maka Anda dapat dengan aman berasumsi itu adalah UTF-8. Karena aturan validasi ketat UTF-8, positif palsu sangat jarang terjadi.

ISO-8859-1 vs. windows-1252

Satu-satunya perbedaan antara dua pengkodean ini adalah bahwa ISO-8859-1 memiliki karakter kontrol C1 di mana windows-1252 memiliki karakter yang dapat dicetak € ‚„… † ‡ ˆ Š Š ‹ŒŽ ''“ ”• –—˜ ™ š› œžŸ. Saya telah melihat banyak file yang menggunakan tanda kutip keriting atau tanda hubung, tetapi tidak ada yang menggunakan karakter kontrol C1. Jadi jangan repot-repot dengan mereka, atau ISO-8859-1, cukup deteksi windows-1252 sebagai gantinya.

Sekarang Anda hanya memiliki satu pertanyaan.

Bagaimana Anda membedakan MacRoman dari cp1252?

Ini jauh lebih rumit.

Karakter tidak terdefinisi

Byte 0x81, 0x8D, 0x8F, 0x90, 0x9D tidak digunakan di windows-1252. Jika itu terjadi, maka asumsikan datanya adalah MacRoman.

Karakter identik

Byte 0xA2 (¢), 0xA3 (£), 0xA9 (©), 0xB1 (±), 0xB5 (µ) kebetulan sama di kedua pengkodean. Jika ini adalah satu-satunya byte non-ASCII, tidak masalah apakah Anda memilih MacRoman atau cp1252.

Pendekatan statistik

Hitung frekuensi karakter (BUKAN byte!) Dalam data yang Anda ketahui sebagai UTF-8. Tentukan karakter yang paling sering. Kemudian gunakan data ini untuk menentukan apakah karakter cp1252 atau MacRoman lebih umum.

Misalnya, dalam pencarian yang baru saja saya lakukan pada 100 artikel Wikipedia bahasa Inggris acak, karakter non-ASCII yang paling umum adalah ·•–é°®’èö—. Berdasarkan fakta ini,

  • Byte 0x92, 0x95, 0x96, 0x97, 0xAE, 0xB0, 0xB7, 0xE8, 0xE9, atau 0xF6 menyarankan windows-1252.
  • Byte 0x8E, 0x8F, 0x9A, 0xA1, 0xA5, 0xA8, 0xD0, 0xD1, 0xD5, atau 0xE1 menyarankan MacRoman.

Hitung byte yang menyarankan cp1252 dan byte yang menyarankan MacRoman, dan gunakan mana yang terbesar.

dan04
sumber
6
Saya telah menerima jawaban Anda karena tidak ada orang yang lebih baik yang muncul dengan sendirinya, dan Anda melakukan pekerjaan yang baik dengan menulis masalah-masalah yang selama ini saya mainkan. Saya memang memiliki program untuk mengendus byte tersebut, meskipun Anda memiliki sekitar dua kali jumlah yang saya dapatkan sendiri.
tchrist
10
Akhirnya sempat menerapkan ini. Ternyata Wikipedia bukanlah data pelatihan yang bagus. Dari 1k artikel en.wikipedia acak, tidak termasuk bagian BAHASA, saya mendapat 50k titik kode unASCII, tetapi distribusinya tidak dapat dipercaya: titik tengah dan peluru terlalu tinggi, & c & c & c. Jadi saya menggunakan korpus Akses Terbuka PubMed semua UTF8, menambang + 14M titik kode unASCII. Saya menggunakan ini untuk membangun model frekuensi relatif dari semua pengkodean 8-bit, lebih bagus dari milik Anda tetapi berdasarkan ide itu. Ini membuktikan sangat prediktif dari pengkodean untuk teks biomedis, domain target. Saya harus mempublikasikan ini. Terima kasih!
tchrist
5
Saya tidak masih memiliki file MacRoman, tetapi tidak akan menggunakan CR karena pembatas baris memberikan pengujian yang berguna. Ini akan bekerja untuk versi Mac OS yang lebih lama, meskipun saya tidak tahu tentang OS9.
Miliways
10

Mozilla nsUniversalDetector (Perl bindings: Encode :: Detect / Encode :: Detect :: Detector ) terbukti jutaan kali lipat .

daxim
sumber
Dokumentasi lebih lanjut ditemukan di sini: mozilla.org/projects/intl/detectorsrc.html , dari sana, disarankan bahwa jika Anda menggali ke dalam dokumen Anda dapat menemukan rangkaian karakter yang didukung
Joel Berger
@ Joel: Saya telah menggali sumbernya. Itu adalah pertanyaan retoris. x-mac-cyrillicdidukung, x-mac-hebrewdibahas panjang lebar di komentar, x-mac-anything-elsetidak disebutkan.
John Machin
@ John Machin: aneh bahwa cyrillic dan ibrani mendapat anggukan, tapi tidak ada yang lain. Saya hanya melempar di sumber dokumentasi lain, saya belum membaca lebih lanjut, terima kasih telah melakukannya!
Joel Berger
7

Upaya saya pada heuristik seperti itu (dengan asumsi Anda telah mengesampingkan ASCII dan UTF-8):

  • Jika 0x7f hingga 0x9f tidak muncul sama sekali, itu mungkin ISO-8859-1, karena kode kontrol tersebut sangat jarang digunakan.
  • Jika 0x91 hingga 0x94 muncul di banyak, kemungkinan itu adalah Windows-1252, karena itu adalah "tanda kutip cerdas", yang sejauh ini merupakan karakter yang paling mungkin digunakan dalam teks bahasa Inggris. Untuk lebih pasti, Anda bisa mencari pasangan.
  • Jika tidak, ini adalah MacRoman, terutama jika Anda melihat banyak 0xd2 hingga 0xd5 (di situlah tanda petik tipografik ada di MacRoman).

Catatan samping:

Untuk file seperti sumber Java yang tidak memiliki fasilitas internal untuk file tersebut, Anda akan meletakkan pengkodean sebelum ekstensi, seperti SomeClass-utf8.java

Jangan lakukan ini!!

Kompilator Java mengharapkan nama file untuk mencocokkan nama kelas, jadi mengganti nama file akan membuat kode sumber tidak dapat dikompilasi. Hal yang benar adalah menebak pengkodean, kemudian menggunakan native2asciialat untuk mengonversi semua karakter non-ASCII menjadi urutan pelolosan Unicode .

Michael Borgwardt
sumber
7
Kompilor bodoh! Tidak, kami tidak dapat memberi tahu orang bahwa mereka hanya dapat menggunakan ASCII; ini bukan tahun 1960-an lagi. Ini tidak akan menjadi masalah jika ada anotasi @encoding sehingga fakta bahwa sumber dalam pengkodean tertentu tidak dipaksa untuk disimpan di luar kode sumber, kekurangan yang benar-benar konyol dari Java yang baik Perl maupun Python tidak menderita. . Itu harus di sumbernya. Itu bukanlah masalah utama kami; itu 1000-an *.textfile.
tchrist
3
@tchrist: Sebenarnya tidak terlalu sulit untuk menulis pemroses anotasi Anda sendiri untuk mendukung anotasi semacam itu. Masih merupakan pengawasan yang memalukan untuk tidak memilikinya dalam API standar.
Michael Borgwardt
Meskipun Java mendukung @encoding, itu tidak akan memastikan deklarasi encoding benar .
dan04
4
@ dan04: Anda dapat mengatakan hal yang sama tentang deklarasi encoding dalam XML, HTML, atau di mana pun. Tetapi seperti contoh-contoh tersebut, jika itu didefinisikan dalam API Standar, sebagian besar alat yang bekerja dengan kode sumber (terutama editor dan IDE) akan mendukungnya, yang akan cukup andal mencegah orang secara tidak sengaja membuat file yang pengenkodean kontennya tidak cocok. deklarasi tersebut.
Michael Borgwardt
4
"Kompilator Java mengharapkan nama file cocok dengan nama kelas." Aturan ini hanya berlaku jika file tersebut mendefinisikan kelas publik tingkat atas.
Matthew Flaschen
6

"Perl, C, Java, atau Python, dan dalam urutan itu": sikap menarik :-)

"Kami memiliki perubahan yang baik dalam mengetahui apakah sesuatu mungkin UTF-8": Sebenarnya kemungkinan bahwa file yang berisi teks bermakna yang dikodekan dalam beberapa rangkaian karakter lain yang menggunakan byte set-bit tinggi akan berhasil didekode karena UTF-8 semakin kecil.

Strategi UTF-8 (dalam bahasa yang paling tidak disukai):

# 100% Unicode-standard-compliant UTF-8
def utf8_strict(text):
    try:
        text.decode('utf8')
        return True
    except UnicodeDecodeError:
        return False

# looking for almost all UTF-8 with some junk
def utf8_replace(text):
    utext = text.decode('utf8', 'replace')
    dodgy_count = utext.count(u'\uFFFD') 
    return dodgy_count, utext
    # further action depends on how large dodgy_count / float(len(utext)) is

# checking for UTF-8 structure but non-compliant
# e.g. encoded surrogates, not minimal length, more than 4 bytes:
# Can be done with a regex, if you need it

Setelah Anda memutuskan bahwa itu bukan ASCII atau UTF-8:

Detektor charset asal Mozilla yang saya tahu tidak mendukung MacRoman dan bagaimanapun juga tidak melakukan pekerjaan dengan baik pada charset 8-bit terutama dengan bahasa Inggris karena AFAICT mereka bergantung pada memeriksa apakah decoding masuk akal dalam pemberian bahasa, mengabaikan karakter tanda baca, dan berdasarkan pilihan dokumen yang luas dalam bahasa tersebut.

Seperti yang dikatakan orang lain, Anda benar-benar hanya memiliki karakter tanda baca set-bit tinggi yang tersedia untuk membedakan antara cp1252 dan macroman. Saya menyarankan untuk melatih model tipe Mozilla pada dokumen Anda sendiri, bukan Shakespeare atau Hansard atau Alkitab KJV, dan memperhitungkan semua 256 byte. Saya berasumsi bahwa file Anda tidak memiliki markup (HTML, XML, dll) di dalamnya - itu akan mengubah probabilitas menjadi sesuatu yang mengejutkan.

Anda telah menyebutkan file yang sebagian besar UTF-8 tetapi gagal didekode. Anda juga harus sangat curiga terhadap:

(1) file yang diduga dienkode dalam ISO-8859-1 tetapi berisi "karakter kontrol" dalam rentang 0x80 hingga 0x9F inklusif ... ini sangat umum sehingga draf standar HTML5 mengatakan untuk mendekode SEMUA aliran HTML yang dideklarasikan sebagai ISO-8859 -1 menggunakan cp1252.

(2) file yang mendekode OK sebagai UTF-8 tetapi Unicode yang dihasilkan berisi "karakter kontrol" dalam kisaran U + 0080 hingga U + 009F inklusif ... ini dapat dihasilkan dari transcoding cp1252 / cp850 (lihat itu terjadi!) / Etc file dari "ISO-8859-1" ke UTF-8.

Latar belakang: Saya memiliki proyek basah-Minggu-sore untuk membuat detektor charset berbasis Python yang berorientasi file (bukan berorientasi web) dan bekerja dengan baik dengan set karakter 8-bit termasuk legacy ** nyang seperti cp850 dan cp437. Ini belum mendekati jam tayang utama. Saya tertarik dengan file pelatihan; apakah file ISO-8859-1 / cp1252 / MacRoman Anda sama-sama "tidak terbebani" seperti yang Anda harapkan dari solusi kode siapa pun?

John Machin
sumber
1
alasan pengurutan bahasa adalah lingkungan. Sebagian besar aplikasi utama kami cenderung dalam java dan utilitas minor dan beberapa aplikasi dalam perl. Kami memiliki sedikit kode di sana-sini yang ada di python. Saya kebanyakan adalah programmer C dan perl, setidaknya dengan pilihan pertama, jadi saya mencari solusi java untuk dicolokkan ke perpustakaan aplikasi kami, atau perpustakaan perl untuk hal yang sama. Jika C, saya dapat membuat lapisan lem XS untuk menghubungkannya ke antarmuka perl, tetapi saya belum pernah melakukannya dengan python sebelumnya.
tchrist
3

Seperti yang telah Anda temukan, tidak ada cara sempurna untuk memecahkan masalah ini, karena tanpa pengetahuan implisit tentang pengkodean yang digunakan file, semua pengkodean 8-bit persis sama: Kumpulan byte. Semua byte valid untuk semua pengkodean 8-bit.

Yang terbaik yang dapat Anda harapkan, adalah semacam algoritma yang menganalisis byte, dan berdasarkan probabilitas byte tertentu yang digunakan dalam bahasa tertentu dengan pengkodean tertentu akan menebak apa pengkodean yang digunakan file. Tetapi itu harus mengetahui bahasa mana yang digunakan file tersebut, dan menjadi sama sekali tidak berguna ketika Anda memiliki file dengan penyandian campuran.

Sisi baiknya, jika Anda tahu bahwa teks dalam file ditulis dalam bahasa Inggris, maka Anda tidak akan melihat perbedaan apa pun pengkodean yang Anda putuskan untuk digunakan untuk file itu, karena perbedaan antara semua pengkodean yang disebutkan semuanya dilokalkan di bagian pengkodean yang menentukan karakter yang biasanya tidak digunakan dalam bahasa Inggris. Anda mungkin mengalami beberapa masalah di mana teks menggunakan format khusus, atau versi khusus tanda baca (misalnya CP1252 memiliki beberapa versi karakter kutipan), tetapi untuk inti teks mungkin tidak akan ada masalah.

Epcylon
sumber
1

Jika Anda dapat mendeteksi setiap encoding KECUALI untuk macroman, maka akan logis untuk mengasumsikan bahwa encoding yang tidak dapat diuraikan ada di macroman. Dengan kata lain, buat saja daftar file yang tidak dapat diproses dan tangani file tersebut seolah-olah file tersebut adalah macroman.

Cara lain untuk mengurutkan file-file ini adalah dengan membuat program berbasis server yang memungkinkan pengguna untuk memutuskan pengkodean mana yang tidak kacau. Tentu saja, ini akan dilakukan di dalam perusahaan, tetapi dengan 100 karyawan melakukan beberapa setiap hari, Anda akan memiliki ribuan file yang diselesaikan dalam waktu singkat.

Akhirnya, bukankah lebih baik hanya mengonversi semua file yang ada ke satu format, dan mengharuskan file baru dalam format itu.

Eric Pauley
sumber
5
Lucu! Ketika saya pertama kali membaca komentar ini setelah diinterupsi selama 30 menit, saya membaca "macroman" sebagai "macro man" dan tidak membuat koneksi dengan MacRoman sampai saya menjalankan pencarian untuk string tersebut untuk melihat apakah OP telah menyebutkannya
Adrian Pronk
+1 jawaban ini agak menarik. tidak yakin apakah itu ide yang baik atau buruk. Adakah yang bisa memikirkan pengkodean yang ada yang mungkin juga tidak terdeteksi? apakah mungkin akan ada di masa depan?
nama pengguna
1

Apakah ada orang lain yang memiliki masalah jutaan file teks lama yang dikodekan secara acak? Jika ya, bagaimana Anda mencoba mengatasinya, dan seberapa sukses Anda?

Saat ini saya sedang menulis program yang menerjemahkan file ke XML. Itu harus mendeteksi otomatis jenis setiap file, yang merupakan superset dari masalah menentukan pengkodean file teks. Untuk menentukan pengkodean saya menggunakan pendekatan Bayesian. Artinya, kode klasifikasi saya menghitung kemungkinan (kemungkinan) bahwa file teks memiliki pengkodean tertentu untuk semua pengkodean yang dipahami. Program kemudian memilih decoder yang paling mungkin. Pendekatan Bayesian bekerja seperti ini untuk setiap pengkodean.

  1. Tetapkan probabilitas awal ( sebelumnya ) bahwa file sedang dienkode, berdasarkan frekuensi setiap enkode.
  2. Periksa setiap byte secara bergantian di file. Cari nilai byte untuk menentukan korelasi antara nilai byte yang ada dan file yang sebenarnya berada dalam pengkodean itu. Gunakan korelasi tersebut untuk menghitung ( posterior probabilitas ) baru bahwa file sedang dalam penyandian. Jika Anda memiliki lebih banyak byte untuk diperiksa, gunakan probabilitas posterior byte tersebut sebagai probabilitas prior saat Anda memeriksa byte berikutnya.
  3. Ketika Anda sampai di akhir file (saya sebenarnya hanya melihat 1024 byte pertama), proabilitas yang Anda miliki adalah probabilitas bahwa file tersebut dalam pengkodean.

Terungkap bahwa teorema Bayes menjadi sangat mudah dilakukan jika alih-alih menghitung probabilitas, Anda menghitung konten informasi , yang merupakan logaritma dari peluang :info = log(p / (1.0 - p)) .

Anda harus menghitung probabilitas initail priori, dan korelasinya, dengan memeriksa korpus file yang telah Anda klasifikasikan secara manual.

Raedwald
sumber