Tentang apakah normalisasi UTF-8?

129

Proyek ICU (yang juga sekarang memiliki perpustakaan PHP ) berisi kelas-kelas yang diperlukan untuk membantu menormalkan string UTF-8 untuk membuatnya lebih mudah untuk membandingkan nilai saat mencari.

Namun, saya mencoba mencari tahu apa artinya ini untuk aplikasi. Misalnya, dalam kasus apa saya ingin "Canonical Equivalence" daripada "Compatibilityivalence", atau sebaliknya?

Xeoncross
sumber
230
? Siapa yang ̸͢k̵͟n̴͘ǫw̸̛s͘ w͘͢ḩ̵a҉̡͢t kengerian terletak pada Dark Heart Unicode ͞
ObscureRobot
@ObscureRobot Saya benar-benar ingin tahu apakah simbol-simbol tambahan tersebut dapat memiliki status atau tidak
eonil
1
@Eonil - Saya tidak yakin apa artinya status dalam konteks unicode.
ObscureRobot
@ObscureRobot Misalnya, beberapa titik kode seperti ini: (begin curved line) (char1) (char2) … (charN) (end curved line)daripada ini: (curved line marker prefix) (char1) (curved line marker prefix) (char2) (curved line marker prefix) (char2). Dengan kata lain, unit minimal yang bisa dirender?
eonil
2
Itu terdengar seperti pertanyaan yang bagus.
ObscureRobot

Jawaban:

181

Semua yang Anda Tidak Pernah Ingin Tahu tentang Normalisasi Unicode

Normalisasi Kanonik

Unicode mencakup banyak cara untuk menyandikan beberapa karakter, terutama karakter beraksen. Normalisasi kanonik mengubah titik kode menjadi bentuk penyandian kanonik. Poin kode yang dihasilkan akan tampak identik dengan yang asli kecuali ada bug di font atau mesin rendering.

Kapan Harus Digunakan

Karena hasilnya tampak identik, selalu aman untuk menerapkan normalisasi kanonik ke string sebelum menyimpan atau menampilkannya, selama Anda dapat mentoleransi hasilnya agar tidak sedikit demi sedikit identik dengan input.

Normalisasi kanonik datang dalam 2 bentuk: NFD dan NFC. Keduanya setara dalam arti bahwa seseorang dapat mengkonversi antara dua bentuk ini tanpa kehilangan. Membandingkan dua string di bawah NFC akan selalu memberikan hasil yang sama dengan membandingkannya di bawah NFD.

NFD

NFD memiliki karakter sepenuhnya diperluas. Ini adalah bentuk normalisasi yang lebih cepat untuk dihitung, tetapi hasilnya lebih banyak poin kode (yaitu menggunakan lebih banyak ruang).

Jika Anda hanya ingin membandingkan dua string yang belum dinormalisasi, ini adalah bentuk normalisasi yang lebih disukai kecuali Anda tahu Anda membutuhkan normalisasi kompatibilitas.

NFC

NFC menggabungkan kembali poin kode bila memungkinkan setelah menjalankan algoritma NFD. Ini membutuhkan waktu sedikit lebih lama, tetapi menghasilkan string yang lebih pendek.

Normalisasi Kompatibilitas

Unicode juga mencakup banyak karakter yang benar-benar bukan milik, tetapi digunakan dalam rangkaian karakter sebelumnya. Unicode menambahkan ini untuk memungkinkan teks dalam set karakter tersebut diproses sebagai Unicode, dan kemudian dikonversi kembali tanpa kehilangan.

Normalisasi kompatibilitas mengonversi ini ke urutan yang sesuai dari karakter "nyata", dan juga melakukan normalisasi kanonik. Hasil normalisasi kompatibilitas mungkin tidak tampak identik dengan aslinya.

Karakter yang menyertakan informasi pemformatan diganti dengan karakter yang tidak. Misalnya karakter dikonversi ke 9. Lainnya tidak melibatkan perbedaan format. Misalnya karakter angka romawi dikonversi ke huruf biasa IX.

Tentunya, setelah transformasi ini dilakukan, tidak mungkin lagi untuk mengubah kembali lossless ke set karakter asli.

Kapan harus digunakan

Konsorsium Unicode menyarankan pemikiran normalisasi kompatibilitas seperti ToUpperCasetransformasi. Ini adalah sesuatu yang mungkin berguna dalam beberapa keadaan, tetapi Anda tidak boleh hanya menerapkannya saja.

Kasing yang bagus akan menjadi mesin pencari karena Anda mungkin ingin mencari yang 9cocok .

Satu hal yang mungkin tidak boleh Anda lakukan adalah menampilkan hasil penerapan normalisasi kompatibilitas kepada pengguna.

NFKC / NFKD

Bentuk normalisasi kompatibilitas datang dalam dua bentuk NFKD dan NFKC. Mereka memiliki hubungan yang sama seperti antara NFD dan C.

Setiap string dalam NFKC secara inheren juga dalam NFC, dan sama untuk NFKD dan NFD. Jadi NFKD(x)=NFD(NFKC(x)), dan NFKC(x)=NFC(NFKD(x)), dll.

Kesimpulan

Jika ragu, lakukan normalisasi kanonik. Pilih NFC atau NFD berdasarkan pertukaran ruang / kecepatan yang berlaku, atau berdasarkan apa yang dibutuhkan oleh sesuatu yang Anda inter-operasi.

Kevin Cathcart
sumber
42
Referensi cepat untuk mengingat singkatan dari: NF = bentuk dinormalisasi D = dekomposisi (dekompresi) , C = tulis (kompres) K = kompatibilitas (karena "C" diambil).
Mike Spross
12
Anda selalu ingin NFD semua string pada input sebagai hal pertama, dan NFC semua string output sebagai hal terakhir. Ini sudah terkenal.
tchrist
3
@tchrist: Itu saran yang umumnya baik, kecuali dalam kasus yang jarang terjadi di mana Anda menginginkan output menjadi byte untuk byte yang identik dengan input ketika tidak ada perubahan yang dilakukan. Ada beberapa kasus lain di mana Anda ingin NFC dalam memori atau NFD pada disk, tetapi mereka adalah pengecualian daripada aturannya.
Kevin Cathcart
@Kevin: Ya, NFD masuk dan NFC keluar akan menghancurkan lajang. Saya tidak yakin ada yang peduli tentang itu, tetapi mungkin.
tchrist
2
Anda mungkin berpikir begitu, tetapi dari lampiran: "Untuk mengubah string Unicode menjadi Bentuk Normalisasi Unicode yang diberikan, langkah pertama adalah sepenuhnya menguraikan string". Dengan demikian bahkan ketika kita menjalankan NFC, Q-Caron pertama-tama akan menjadi Q + Caron, dan tidak dapat menyusun ulang, karena aturan stabilitas melarang penambahan pemetaan komposisi baru. NFC secara efektif didefinisikan sebagai NFC(x)=Recompose(NFD(x)).
Kevin Cathcart
40

Beberapa karakter, misalnya huruf dengan aksen (katakanlah é) dapat direpresentasikan dalam dua cara - satu titik kode U+00E9atau huruf biasa diikuti dengan tanda aksen yang menggabungkan U+0065 U+0301. Normalisasi biasa akan memilih salah satunya untuk selalu mewakilinya (titik kode tunggal untuk NFC, bentuk penggabungan untuk NFD).

Untuk karakter yang dapat diwakili oleh beberapa urutan karakter dasar dan menggabungkan tanda (katakanlah, "s, titik di bawah, titik di atas" vs menempatkan titik di atas kemudian di bawah titik atau menggunakan karakter dasar yang sudah memiliki salah satu dari titik-titik), NFD akan juga memilih salah satu dari ini (di bawah ini berjalan lebih dulu, seperti yang terjadi)

Dekomposisi kompatibilitas menyertakan sejumlah karakter yang "seharusnya tidak benar-benar" menjadi karakter tetapi karena mereka digunakan dalam pengkodean sebelumnya. Normalisasi biasa tidak akan menyatukan ini (untuk menjaga integritas bolak-balik - ini bukan masalah untuk menggabungkan formulir karena tidak ada penyandian sebelumnya [kecuali beberapa penyandian Vietnam] menggunakan keduanya), tetapi normalisasi kompatibilitas akan. Pikirkan seperti tanda "kg" kilogram yang muncul dalam beberapa penyandian Asia Timur (atau katakana dan alfabet setengah lebar / lebar pita lebar), atau ligatur "fi" di MacRoman.

Lihat http://unicode.org/reports/tr15/ untuk lebih jelasnya.

Random832
sumber
1
Ini memang jawaban yang benar. Jika Anda hanya menggunakan normalisasi kanonik pada teks yang berasal dari beberapa set karakter lawas, hasilnya dapat dikonversi kembali ke set karakter itu tanpa kehilangan. Jika Anda menggunakan dekomposisi kompatibilitas, Anda berakhir tanpa karakter kompatibilitas apa pun, tetapi tidak mungkin lagi untuk mengkonversi kembali ke set karakter asli tanpa kehilangan.
Kevin Cathcart
13

Bentuk normal (dari Unicode, bukan basis data) berurusan terutama (secara eksklusif?) Dengan karakter yang memiliki tanda diakritik. Unicode memberi beberapa karakter tanda "built in" diakritik, seperti U + 00C0, "Latin Capital A with Grave". Karakter yang sama dapat dibuat dari `Latin Capital A" (U + 0041) dengan "Combining Grave Accent" (U + 0300). Itu berarti meskipun dua sekuens menghasilkan karakter hasil yang sama, byte-by-byte perbandingan akan menunjukkan bahwa mereka sama sekali berbeda.

Normalisasi adalah upaya menghadapi itu. Normalisasi menjamin (atau setidaknya mencoba) bahwa semua karakter dikodekan dengan cara yang sama - baik semua menggunakan tanda gabungan diakritik terpisah jika diperlukan, atau semua menggunakan titik kode tunggal sedapat mungkin. Dari sudut pandang perbandingan, tidak banyak masalah yang Anda pilih - cukup banyak string yang dinormalisasi akan membandingkan dengan benar dengan string yang dinormalisasi lainnya.

Dalam hal ini, "kompatibilitas" berarti kompatibilitas dengan kode yang mengasumsikan bahwa satu titik kode sama dengan satu karakter. Jika Anda memiliki kode seperti itu, Anda mungkin ingin menggunakan formulir normal kompatibilitas. Meskipun saya belum pernah melihatnya secara langsung, nama-nama bentuk normal menyiratkan bahwa konsorsium Unicode menganggapnya lebih baik menggunakan tanda gabungan diakritik terpisah. Ini membutuhkan lebih banyak kecerdasan untuk menghitung karakter aktual dalam string (serta hal-hal seperti memecah string secara cerdas), tetapi lebih fleksibel.

Jika Anda menggunakan ICU sepenuhnya, kemungkinan Anda ingin menggunakan bentuk normal kanonik. Jika Anda mencoba menulis kode sendiri yang (misalnya) mengasumsikan titik kode sama dengan karakter, maka Anda mungkin menginginkan bentuk normal kompatibilitas yang menjadikannya sesering mungkin.

Jerry Coffin
sumber
Jadi ini adalah bagian di mana Fungsi Grapheme masuk kemudian. Tidak hanya karakter lebih banyak byte daripada ASCII - tetapi beberapa urutan bisa menjadi karakter tunggal, bukan? (Berbeda dengan fungsi string MB .)
Xeoncross
4
Tidak, 'satu titik kode adalah satu karakter' kira-kira sesuai dengan NFC (yang memiliki tanda penggabungan adalah NFD, dan keduanya tidak merupakan "kompatibilitas") - Normalisasi kompatibilitas NFKC / NFKD adalah masalah yang berbeda; kompatibilitas (atau ketiadaan) untuk pengkodean lawas yang misalnya memiliki karakter terpisah untuk yunani mu dan 'mikro' (itu menyenangkan untuk ditampilkan karena versi "kompatibilitas" adalah yang ada di blok 1 Latin)
Random832
@ Random832: Ups, benar sekali. Saya harus tahu lebih baik daripada pergi dari memori ketika saya belum bekerja dengannya selama satu atau dua tahun terakhir.
Jerry Coffin
@ Random832 Itu tidak benar. "Kira-kira" Anda terlalu di luar sana. Pertimbangkan dua grafem, ō̲̃ dan ȭ̲. Ada banyak cara untuk menulis masing-masing, yang mana masing-masing adalah NFC dan satu NFD, tetapi yang lain juga ada. Tidak ada kasus bahwa hanya satu titik kode. NFD untuk yang pertama adalah "o\x{332}\x{303}\x{304}", dan NFC adalah "\x{22D}\x{332}". Untuk NFD kedua adalah "o\x{332}\x{304}\x{303}"dan NFC "\x{14D}\x{332}\x{303}". Namun, ada banyak kemungkinan non-kanonik yang secara kanonik setara dengan ini. Normalisasi memungkinkan perbandingan biner dari grafem setara kanonik.
tchrist
5

Jika dua string unicode setara dengan kanonik string benar-benar sama, hanya menggunakan urutan unicode yang berbeda. Misalnya Ä dapat direpresentasikan dengan menggunakan karakter Ä atau kombinasi A dan ◌̈.

Jika string hanya setara dengan kompatibilitas, string tidak harus sama, tetapi mereka mungkin sama dalam beberapa konteks. Misalnya ff dapat dianggap sama dengan ff.

Jadi, jika Anda membandingkan string, Anda harus menggunakan kesetaraan kanonik, karena kesetaraan kompatibilitas bukan kesetaraan nyata.

Tetapi jika Anda ingin mengurutkan serangkaian string, mungkin masuk akal untuk menggunakan kesetaraan kompatibilitas karena hampir identik.

NikiC
sumber
5

Ini sebenarnya cukup sederhana. UTF-8 sebenarnya memiliki beberapa representasi berbeda dari "karakter" yang sama. (Saya menggunakan karakter dalam tanda kutip karena byte-bijaksana mereka berbeda, tetapi praktis mereka sama). Contoh diberikan dalam dokumen tertaut.

Karakter "Ç" dapat direpresentasikan sebagai urutan byte 0xc387. Tetapi juga dapat diwakili oleh C(0x43) diikuti oleh urutan byte 0xcca7. Jadi Anda dapat mengatakan bahwa 0xc387 dan 0x43cca7 adalah karakter yang sama. Alasan yang berhasil, adalah 0xcca7 adalah tanda gabungan; artinya mengatakan dibutuhkan karakter sebelum (di Csini), dan memodifikasinya.

Sekarang, sejauh perbedaan antara kesetaraan kanonik vs kesetaraan kompatibilitas, kita perlu melihat karakter secara umum.

Ada 2 jenis karakter, yang menyampaikan makna melalui nilai , dan yang mengambil karakter lain dan mengubahnya. 9 adalah karakter yang bermakna. Skrip super ⁹ mengambil makna itu dan mengubahnya dengan presentasi. Jadi secara kanonik mereka memiliki makna yang berbeda, tetapi mereka masih mewakili karakter dasar.

Kesetaraan kanonik adalah di mana urutan byte menghasilkan karakter yang sama dengan makna yang sama. Kesetaraan kompatibilitas adalah ketika urutan byte menghasilkan karakter yang berbeda dengan arti dasar yang sama (meskipun mungkin diubah). 9 dan ⁹ adalah setara kompatibilitas karena keduanya berarti "9", tetapi tidak setara secara kanonik karena mereka tidak memiliki representasi yang sama.

ircmaxell
sumber
@tchrist: Baca lagi jawabannya. Saya bahkan tidak pernah menyebutkan cara berbeda untuk mewakili titik kode yang sama. Saya mengatakan ada beberapa cara untuk mewakili karakter cetak yang sama (melalui kombinator dan beberapa karakter). Yang berlaku untuk UTF-8 dan Unicode. Jadi downvote dan komentar Anda tidak benar-benar berlaku untuk apa yang saya katakan. Bahkan, pada dasarnya saya membuat poin yang sama dengan yang dibuat poster teratas (walaupun tidak juga) ...
ircmaxell
4

Apakah kesetaraan kanonik atau kesetaraan kompatibilitas lebih relevan bagi Anda tergantung pada aplikasi Anda. Cara berpikir ASCII tentang perbandingan string secara kasar memetakan ke ekuivalensi kanonik, tetapi Unicode mewakili banyak bahasa. Saya rasa tidak aman untuk menganggap bahwa Unicode mengkodekan semua bahasa dengan cara yang memungkinkan Anda memperlakukan mereka seperti ASCII Eropa Barat.

Gambar 1 dan 2 memberikan contoh yang baik dari dua jenis kesetaraan. Di bawah kesetaraan kompatibilitas, sepertinya angka yang sama dalam bentuk sub-dan super-script akan membandingkan sama. Tapi saya tidak yakin itu menyelesaikan masalah yang sama seperti bentuk arab kursif atau karakter yang diputar.

Kebenaran sulit dari pemrosesan teks Unicode adalah bahwa Anda harus berpikir secara mendalam tentang persyaratan pemrosesan teks aplikasi Anda, dan kemudian mengatasinya sebaik mungkin dengan alat yang tersedia. Itu tidak secara langsung menjawab pertanyaan Anda, tetapi jawaban yang lebih terperinci akan membutuhkan ahli linguistik untuk setiap bahasa yang Anda harapkan untuk didukung.

ObscureRobot
sumber
1

Masalah membandingkan string : dua string dengan konten yang setara untuk keperluan sebagian besar aplikasi mungkin mengandung urutan karakter yang berbeda.

Lihat kesetaraan kanonik Unicode : jika algoritma perbandingan sederhana (atau harus cepat), kesetaraan Unicode tidak dilakukan. Masalah ini terjadi, misalnya, dalam perbandingan kanonik XML, lihat http://www.w3.org/TR/xml-c14n

Untuk menghindari masalah ini ... Standar apa yang digunakan? "extended UTF8" atau "compact UTF8"?
Gunakan "ç" atau "c + ◌̧."?

W3C dan lainnya (mis. Nama file ) menyarankan untuk menggunakan "disusun sebagai kanonik" (perhatikan C dari string "paling ringkas" lebih pendek) ... Jadi,

Standarnya adalah C ! ragu menggunakan NFC

Untuk interoperabilitas, dan untuk pilihan "konvensi konfigurasi" , rekomendasinya adalah penggunaan NFC , untuk "mengkanonisasi" string eksternal. Untuk menyimpan XML kanonik, misalnya, simpan di "FORM_C". CSV W3C pada Kelompok Kerja Web juga merekomendasikan NFC (bagian 7.2).

PS: de "FORM_C" adalah bentuk default di sebagian besar perpustakaan. Ex. dalam normalizer.isnormalized PHP () .


Istilah " bentuk kompos " ( FORM_C) digunakan untuk keduanya, untuk mengatakan bahwa "string dalam bentuk C-kanonik" (hasil dari transformasi NFC) dan untuk mengatakan bahwa algoritma transformasi digunakan ... Lihat http: //www.macchiato.com/unicode/nfc-faq

(...) masing-masing dari urutan berikut (dua yang pertama adalah urutan karakter tunggal) mewakili karakter yang sama:

  1. U + 00C5 (Å) SURAT MODAL LATIN A DENGAN CINCIN DI ATAS
  2. U + 212B (Å) TANDA ANGSTROM
  3. U + 0041 (A) SURAT MODAL LATIN A + U + 030A (̊) GABUNG CINCIN DI ATAS

Urutan-urutan ini disebut ekuivalen secara kanonik. Bentuk pertama dari ini disebut NFC - untuk Normalisasi Bentuk C, di mana C adalah untuk kompos . (...) Suatu fungsi yang mengubah string S ke dalam bentuk NFC dapat disingkat toNFC(S), sedangkan yang menguji apakah S dalam NFC disingkat isNFC(S).


Catatan: untuk menguji normalisasi string kecil (UTF-8 murni atau referensi entitas XML), Anda dapat menggunakan tes ini / menormalkan konverter online .

Peter Krauss
sumber
Saya bingung. Saya pergi ke halaman penguji online ini dan saya masukkan di sana: "TÖST MÉ pleasé." dan coba semua 4 normalisasi yang diberikan - tidak ada yang mengubah teks saya dengan cara apa pun, well, kecuali bahwa itu mengubah kode yang digunakan untuk menyajikan karakter tersebut. Apakah saya salah berpikir bahwa "normalisasi" berarti "menghapus semua diakritik dan sejenisnya", dan itu sebenarnya berarti - hanya mengubah pengkodean utf di bawah?
userfuser
Hai @ penggunafuser mungkin Anda memerlukan posisi, tentang aplikasi: apakah membandingkan atau membakukan teks Anda? Posting saya di sini hanya tentang "untuk membakukan" aplikasi. PS: ketika seluruh dunia menggunakan standar, masalah perbandingannya lenyap.
Peter Krauss