Ketika "Zoë"! == "Zoë", atau mengapa Anda perlu menormalkan string Unicode

Pernah mendengar normalisasi Unicode? Kamu tidak sendiri Tetapi semua orang perlu tahu tentang ini. Normalisasi dapat menghemat banyak masalah. Cepat atau lambat, sesuatu yang mirip dengan yang ditunjukkan pada gambar berikut terjadi pada pengembang mana pun.
Zoë bukan Zoë

Dan ini, omong-omong, bukan contoh dari JavaScript aneh lainnya. Penulis materi, terjemahan yang kami terbitkan hari ini, mengatakan bahwa ia dapat menunjukkan bagaimana masalah yang sama memanifestasikan dirinya ketika menggunakan hampir setiap bahasa pemrograman yang ada. Secara khusus, kita berbicara tentang skrip Python, Go, dan bahkan shell. Bagaimana cara mengatasinya?

Latar belakang


Saya pertama kali mengalami masalah Unicode beberapa tahun yang lalu ketika saya menulis sebuah aplikasi (di Objective-C) yang mengimpor daftar kontak dari buku alamat pengguna dan dari jejaring sosialnya, setelah itu saya menghilangkan duplikat. Dalam situasi tertentu, ternyata beberapa orang ada di daftar dua kali. Ini terjadi karena fakta bahwa nama mereka, menurut program, bukan string yang sama.

Meskipun dalam contoh di atas dua baris terlihat persis sama, cara mereka disajikan dalam sistem, byte di mana mereka disimpan pada disk berbeda. Dengan nama depan, "Zoë" karakter ë (e with umlaut) mewakili satu titik kode Unicode. Dalam kasus kedua, kita berurusan dengan dekomposisi, dengan pendekatan untuk mewakili karakter menggunakan beberapa karakter. Jika Anda, dalam aplikasi Anda, bekerja dengan string Unicode, Anda perlu mempertimbangkan fakta bahwa karakter yang sama dapat direpresentasikan dengan cara yang berbeda.

Bagaimana kami sampai pada emoji: secara singkat tentang pengkodean karakter


Komputer bekerja dengan byte, yang hanya angka. Agar dapat memproses teks pada komputer, orang-orang setuju pada korespondensi karakter dan angka, dan mencapai kesepakatan tentang bagaimana representasi visual karakter harus terlihat.

Perjanjian tersebut diwakili oleh pengkodean ASCII (American Standard Code for Information Interchange). Pengkodean ini menggunakan 7 bit dan dapat mewakili 128 karakter, termasuk alfabet Latin (huruf besar dan kecil), angka dan tanda baca dasar. ASCII juga menyertakan banyak karakter "tidak dapat dicetak", seperti umpan baris, tab, carriage return, dan lainnya. Misalnya, dalam ASCII, huruf Latin M (huruf besar m) dikodekan sebagai 77 (4D dalam notasi heksadesimal).

Masalah dengan ASCII adalah meskipun 128 karakter mungkin cukup untuk mewakili semua karakter yang biasanya digunakan orang-orang yang menggunakan teks berbahasa Inggris, jumlah karakter ini tidak cukup untuk mewakili teks dalam bahasa lain dan berbagai karakter khusus seperti emoji.

Solusi untuk masalah ini adalah adopsi standar Unicode, yang ditujukan untuk kemungkinan mewakili setiap karakter yang digunakan dalam semua teks modern dan kuno, termasuk karakter seperti emoji. Misalnya, dalam standar Unicode 12.0 terbaru, ada lebih dari 137.000 karakter.

Standar Unicode dapat diimplementasikan menggunakan berbagai metode pengkodean karakter. Yang paling umum adalah UTF-8 dan UTF-16. Perlu dicatat bahwa dalam ruang web yang paling umum adalah standar untuk pengkodean teks UTF-8.

Standar UTF-8 menggunakan 1 hingga 4 byte untuk mewakili karakter. UTF-8 adalah superset dari ASCII, jadi 128 karakter pertamanya cocok dengan karakter yang diwakili dalam tabel kode ASCII. Standar UTF-16, di sisi lain, menggunakan 2 hingga 4 byte untuk mewakili 1 karakter.

Mengapa keduanya ada standar? Faktanya adalah teks dalam bahasa Barat biasanya paling efisien dikodekan menggunakan standar UTF-8 (karena sebagian besar karakter dalam teks tersebut dapat direpresentasikan sebagai kode ukuran 1 byte). Jika kita berbicara tentang bahasa oriental, maka kita dapat mengatakan bahwa file yang menyimpan teks yang ditulis dalam bahasa ini biasanya berkurang ketika menggunakan UTF-16.

Poin Kode Unicode dan Pengkodean Karakter


Setiap karakter dalam standar Unicode diberikan nomor identifikasi yang disebut titik kode. Misalnya, emoji titik kode adalah U + 1F436 .

Saat menyandikan ikon ini, ini dapat direpresentasikan sebagai berbagai urutan byte:

  • UTF-8: 4 byte, 0xF0 0x9F 0x90 0xB6
  • UTF-16: 4 byte, 0xD83D 0xDC36

Dalam kode JavaScript di bawah ini, ketiga perintah mencetak karakter yang sama ke konsol browser.

//
console.log(' ') // =>
// Unicode (ES2015+)
console.log('\u{1F436}') // =>
// UTF-16
// ( 2 )
console.log('\uD83D\uDC36') // =>


Mekanisme internal sebagian besar penerjemah JavaScript (termasuk Node.js dan browser modern) menggunakan UTF-16. Ini berarti bahwa ikon anjing yang kami pertimbangkan disimpan menggunakan dua unit kode UTF-16 (masing-masing 16 bit). Oleh karena itu, apa yang dihasilkan oleh kode berikut ini seharusnya tidak dapat dimengerti oleh Anda:

console.log(' '.length) // => 2

Kombinasi karakter


Sekarang kembali ke tempat kita mulai, yaitu, mari kita bicara tentang mengapa simbol yang terlihat sama untuk seseorang memiliki representasi internal yang berbeda.

Beberapa karakter Unicode dirancang untuk memodifikasi karakter lain. Mereka disebut menggabungkan karakter. Mereka berlaku untuk karakter dasar, misalnya:

  • n + ˜ = ñ
  • u + ¨ = ü
  • e + ´ = é

Seperti yang dapat Anda lihat dari contoh sebelumnya, karakter yang dapat dikombinasikan memungkinkan Anda untuk menambahkan diakritik ke karakter dasar. Tetapi kemampuan transformasi karakter Unicode tidak terbatas pada ini. Misalnya, beberapa urutan karakter dapat direpresentasikan sebagai ligatur (sehingga Anda dapat berubah menjadi æ).

Masalahnya adalah karakter khusus dapat direpresentasikan dengan berbagai cara.

Misalnya, huruf é dapat direpresentasikan dalam dua cara:

  • Menggunakan titik kode tunggal U + 00E9 .
  • Menggunakan kombinasi huruf e dan tanda akut, yaitu, menggunakan dua titik kode - U + 0065 dan U + 0301 .

Karakter yang dihasilkan dari penggunaan salah satu dari cara-cara ini mewakili huruf é akan terlihat sama, tetapi ketika dibandingkan, ternyata karakternya berbeda. Garis yang berisi mereka akan memiliki panjang yang berbeda. Anda dapat memverifikasi ini dengan menjalankan kode berikut di konsol browser.

 console.log('\u00e9') // => é console.log('\u0065\u0301') // => é console.log('\u00e9' == '\u0065\u0301') // => false console.log('\u00e9'.length) // => 1 console.log('\u0065\u0301'.length) // => 2 

Ini dapat menyebabkan kesalahan yang tidak terduga. Misalnya, mereka dapat diekspresikan dalam kenyataan bahwa program, untuk alasan yang tidak diketahui, tidak dapat menemukan beberapa entri dalam database, di mana pengguna, memasukkan kata sandi yang benar, tidak dapat masuk ke sistem.

Normalisasi garis


Masalah di atas memiliki solusi sederhana, yang terdiri dari normalisasi string, dalam membawa mereka ke "representasi kanonik".

Ada empat bentuk standar (algoritma) normalisasi:

  • NFC: Normalisasi dari Komposisi Canonical.
  • NFD: Normalisasi Membentuk Dekomposisi Canonical.
  • NFKC: Komposisi Kompatibilitas Bentuk Normalisasi.
  • NFKD: Dekomposisi Kompatibilitas Bentuk Normalisasi.

Bentuk normalisasi yang paling umum digunakan adalah NFC. Saat menggunakan algoritma ini, semua karakter didekomposisi terlebih dahulu, setelah itu semua urutan penggabungan disusun kembali dalam urutan yang ditentukan oleh standar. Untuk penggunaan praktis, Anda dapat memilih bentuk apa pun. Yang utama adalah menerapkannya secara konsisten. Akibatnya, penerimaan data yang sama pada input program akan selalu mengarah pada hasil yang sama.

Dalam JavaScript, dimulai dengan standar ES2015 (ES6), ada metode bawaan untuk menormalkan string - String.prototype.normalize ([form]) . Anda dapat menggunakannya di lingkungan Node.js dan di hampir semua browser modern. Argumen form dari metode ini adalah pengidentifikasi string dari bentuk normalisasi. Standarnya adalah bentuk NFC.

Kami kembali ke contoh yang dipertimbangkan sebelumnya, menerapkan normalisasi kali ini:

 const str = '\u0065\u0301' console.log(str == '\u00e9') // => false const normalized = str.normalize('NFC') console.log(normalized == '\u00e9') // => true console.log(normalized.length) // => 1 

Ringkasan


Jika Anda mengembangkan aplikasi web dan menggunakan apa yang dimasukkan pengguna di dalamnya, selalu menormalkan data teks yang diterima. Dalam JavaScript, Anda dapat menggunakan metode string standar normalisasi () untuk melakukan normalisasi.

Pembaca yang budiman! Apakah Anda mengalami masalah dengan string yang dapat diselesaikan dengan normalisasi?

Source: https://habr.com/ru/post/id445274/


All Articles