Namun, di awal pembicaraan saya, saya menyebutkan bahwa ini bukan pemaparan lain dari rangkaian "kesalahpahaman tentang X, yang diyakini oleh pemrogram". Anda dapat menemukan sejumlah wahyu seperti itu. Namun, saya tidak suka artikel ini. Mereka mendaftar berbagai hal yang seharusnya salah, tetapi jarang menjelaskan mengapa ini dan apa yang harus dilakukan. Saya menduga orang-orang hanya akan membaca artikel ini, memberi selamat pada diri mereka sendiri atas pencapaian ini, dan kemudian mencari cara baru yang menarik untuk membuat kesalahan yang tidak disebutkan dalam artikel ini. Ini karena mereka tidak benar-benar memahami masalah yang menyebabkan kesalahan tersebut.
Oleh karena itu, dalam laporan saya, saya mencoba menjelaskan beberapa masalah sebaik mungkin dan menjelaskan cara menyelesaikannya - Saya lebih menyukai pendekatan ini . Salah satu topik yang hanya saya singgung sepintas lalu (itu hanya satu slide dan beberapa sebutan di slide lain) adalah kerumitan yang dapat dikaitkan dengan kasus karakter. Ada Jawaban Benar ™ resmi untuk masalah yang saya diskusikan - perbandingan pengenal yang tidak peka huruf besar / kecil - dan dalam pembicaraan saya memberikan solusi terbaik yang saya tahu hanya dengan menggunakan pustaka standar Python.
Namun, saya secara singkat menyebutkan kompleksitas yang lebih dalam dari kasus karakter Unicode, dan saya ingin meluangkan waktu untuk menjelaskan detailnya. Ini menarik, dan memahaminya dapat membantu Anda membuat keputusan saat merancang dan menulis kode pemrosesan teks. Jadi saya menawarkan kebalikan dari artikel "kesalahpahaman tentang X yang diyakini oleh programmer" - "kebenaran yang harus diketahui oleh programmer."
Satu hal lagi: Unicode penuh dengan terminologi. Pada artikel ini, saya terutama akan menggunakan definisi "huruf besar" dan "huruf kecil", karena standar Unicodemenggunakan istilah ini. Jika Anda menyukai istilah lain seperti huruf kecil / huruf besar, tidak apa-apa. Juga, saya akan sering menggunakan istilah "simbol", yang mungkin dianggap tidak benar oleh beberapa orang. Ya, dalam Unicode konsep "karakter" tidak selalu seperti yang diharapkan orang, jadi sebaiknya hindari dengan menggunakan istilah lain. Namun, dalam artikel ini, saya akan menggunakan istilah seperti yang digunakan di Unicode - untuk menggambarkan entitas abstrak yang dapat diklaim. Jika relevan, saya akan menggunakan istilah yang lebih spesifik seperti poin kode untuk memperjelas.
Ada lebih dari dua register
Penutur asli bahasa Eropa terbiasa dengan fakta bahwa bahasa mereka menggunakan huruf besar untuk menunjukkan hal-hal tertentu. Misalnya, dalam bahasa Inggris [dan Rusia], kami biasanya memulai kalimat dengan huruf besar dan paling sering dilanjutkan dengan huruf kecil. Selain itu, nama diri dimulai dengan huruf besar, dan banyak akronim dan singkatan ditulis dalam huruf besar.
Dan biasanya kami mengira hanya ada dua register. Ada huruf "A" dan ada huruf "a". Satu dalam huruf besar, satu dalam huruf kecil - bukankah begitu?
Namun, ada tiga register di Unicode. Ada huruf besar, ada huruf kecil, dan ada huruf besar [titlecase]. Dalam bahasa Inggris, nama ditulis dengan cara ini. Misalnya, "Avengers: Infinity War". Biasanya, untuk ini, huruf pertama dari setiap kata hanya ditulis dalam huruf besar (dan bergantung pada aturan dan gaya yang berbeda, beberapa kata, seperti artikel, tidak menggunakan huruf besar).
Standar Unicode memberikan contoh karakter dalam huruf kapital: U + 01F2 LATIN CAPITAL LETTER D DENGAN KECIL Z. Terlihat seperti ini: Dz.
Karakter seperti itu terkadang diperlukan untuk menangani konsekuensi negatif dari salah satu solusi paling awal untuk standar Unicode: kompatibilitas mundur dengan pengkodean teks yang ada. Akan lebih mudah bagi Unicode untuk membuat urutan menggunakan kombinasi karakter standar. Namun, di banyak sistem yang ada, ruang telah dialokasikan untuk urutan yang sudah jadi. Misalnya, dalam ISO-8859-1 ("latin-1"), karakter "é" memiliki bentuk siap pakai bernomor 0xe9. Di Unicode, lebih disukai untuk menulis huruf ini dengan "e" dan tanda aksen yang terpisah. Tetapi untuk memastikan kompatibilitas penuh ke belakang dengan pengkodean yang ada seperti latin-1, Unicode juga memberikan poin kode untuk karakter yang sudah jadi. Misalnya, U + 00E9 LATIN SMALL LETTER E DENGAN AKUT.
Meskipun posisi kode karakter ini sama dengan nilai latin-1 byte-nya, Anda tidak boleh mengandalkan ini. Tidak mungkin pengkodean karakter di Unicode akan mempertahankan posisi ini. Misalnya, dalam UTF-8, posisi kode U + 00E9 ditulis sebagai urutan byte 0xc3 0xa9.
Dan, tentu saja, ada karakter dalam pengkodean yang ada yang membutuhkan penanganan khusus saat menggunakan huruf besar, itulah sebabnya mereka dimasukkan dalam Unicode "sebagaimana adanya". Jika Anda ingin melihatnya, cari di database Unicode favorit Anda untuk karakter dari kategori Lt ("Letter, titlecase").
Ada beberapa cara untuk mendefinisikan kasus
Standar Unicode (§4.2) mencantumkan tiga definisi kasus yang berbeda. Mungkin pilihan salah satu dari ketiganya dilakukan untuk Anda oleh bahasa pemrograman Anda; jika tidak, pilihan Anda akan bergantung pada tujuan spesifik Anda. Definisi tersebut adalah:
- Karakter menggunakan huruf besar jika berada dalam kategori Lu ("Huruf, huruf besar"), dan dalam huruf kecil jika termasuk dalam kategori Ll ("Huruf, huruf kecil"). Standar tersebut mengakui batasan dari definisi ini: setiap simbol spesifik harus dikaitkan hanya dengan satu kategori. Karena itu, banyak karakter yang "harus" dalam huruf besar atau kecil tidak akan memenuhi persyaratan ini karena termasuk dalam kategori lain.
- Karakter dalam huruf besar jika mewarisi properti Huruf besar, dan huruf kecil jika mewarisi properti Huruf kecil. Ini adalah kombinasi dari definisi satu dengan properti karakter lainnya, yang mungkin termasuk case.
- Karakter menggunakan huruf besar jika tidak berubah setelah dipetakan menjadi huruf besar. Karakter menggunakan huruf kecil jika tidak berubah setelah dipetakan ke huruf kecil. Ini adalah definisi yang cukup umum, tetapi dapat juga berperilaku non-intuitif.
Jika Anda bekerja dengan subset simbol yang terbatas (khususnya, dengan huruf), maka 1 definisi mungkin cukup untuk Anda. Jika repertoar Anda lebih luas - termasuk simbol seperti huruf yang bukan huruf, definisi kedua mungkin cocok untuk Anda. Direkomendasikan oleh standar Unicode, §4.2:
Pemrogram yang memanipulasi string Unicode harus bekerja dengan fungsi string seperti isLowerCase (dan sepupu fungsionalnya toLowerCase) jika mereka tidak bekerja secara langsung dengan properti karakter.
Fungsi yang disebutkan di sini didefinisikan dalam §3.13 dari standar Unicode. Secara formal, definisi 3 menggunakan fungsi isLowerCase dan isUpperCase dari §3.13, yang didefinisikan dalam istilah posisi tetap masing-masing di toLowerCase dan toUpperCase.
Jika bahasa pemrograman Anda memiliki fungsi untuk memeriksa atau mengubah kasus string atau karakter individu, ada baiknya menyelidiki definisi mana yang digunakan dalam implementasi. Jika Anda tertarik, metode isupper () dan islower () di Python menggunakan definisi ke-2.
Tidak mungkin untuk memahami kasus suatu karakter dari penampilan atau namanya
Dengan munculnya banyak karakter, Anda dapat mengetahui dalam kasus apa mereka. Misalnya, "A" adalah huruf besar. Ini juga jelas dari nama lambangnya: "LATIN CAPITAL LETTER A". Namun, terkadang cara ini tidak berhasil. Ambil titik kode U + 1D34. Ini terlihat seperti ini: ᴴ. Di Unicode, diberi nama: MODIFIER LETTER CAPITAL H. Jadi huruf besar kan?
Faktanya, ini mewarisi properti Huruf kecil, jadi menurut definisi # 2 huruf kecil, meskipun secara visual menyerupai huruf besar H, dan namanya mengandung kata "MODAL".
Beberapa karakter tidak memiliki case sama sekali
Definisi 135 dalam §3.13 dari standar Unicode menyatakan:
C peka huruf besar / kecil jika dan hanya jika C memiliki properti Huruf kecil atau Huruf Besar, atau General_Category adalah Titlecase_Letter.
Ini berarti bahwa banyak karakter Unicode - pada kenyataannya, kebanyakan dari mereka - tidak ada artinya. Pertanyaan tentang kasus mereka tidak masuk akal, dan perubahan kasus tidak memengaruhi mereka. Namun, kita bisa mendapatkan jawaban untuk pertanyaan ini berdasarkan definisi # 3.
Beberapa karakter berperilaku seperti mereka memiliki banyak register
Implikasinya adalah jika Anda menggunakan definisi # 3 dan menanyakan apakah karakter uncased dalam huruf besar atau kecil, Anda akan mendapatkan jawaban "ya".
Standar Unicode memberikan contoh (Tabel 4-1, baris 7) dari karakter U + 02BD MODIFIER LETTER REVERSED COMMA (yang terlihat seperti ini: ʽ). Itu tidak memiliki properti huruf kecil atau huruf besar yang diwariskan, itu tidak termasuk dalam kategori Lt, jadi tidak ada huruf besar / kecil. Pada saat yang sama mengonversi ke huruf besar tidak mengubahnya, dan mengubah ke huruf kecil tidak mengubahnya, oleh karena itu, menurut definisi ke-3, ia menjawab "ya" untuk kedua pertanyaan: "apakah Anda termasuk dalam huruf besar?" dan "apakah kamu huruf kecil?"
Tampaknya ini dapat menyebabkan kebingungan yang tidak perlu, tetapi intinya adalah bahwa definisi # 3 berfungsi dengan urutan karakter Unicode apa pun, dan memungkinkan Anda untuk menyederhanakan algoritme konversi huruf besar (karakter tanpa kasus hanya berubah menjadi dirinya sendiri).
Kasus peka konteks
Anda mungkin berpikir bahwa jika tabel konversi kasus Unicode mencakup semua karakter, maka konversi ini hanya tentang menemukan tempat yang tepat dalam tabel. Misalnya, database Unicode mengatakan U + 0041 LATIN CAPITAL LETTER A adalah huruf kecil U + 0061 LATIN SMALL LETTER A. Sederhana, bukan?
Satu contoh di mana pendekatan ini tidak berhasil adalah bahasa Yunani. Karakter Σ - yaitu, U + 03A3 GREEK CAPITAL LETTER SIGMA - dipetakan ke dua karakter yang berbeda saat dikonversi ke huruf kecil, bergantung pada tempatnya di kata. Jika berada di akhir kata, maka dalam huruf kecil akan menjadi ς (U + 03C2 YUNANI KECIL SURAT AKHIR SIGMA). Di tempat lain itu akan menjadi σ (U + 03C3 YUNANI SURAT KECIL SIGMA).
Artinya register tidak one-to-one atau transitive. Contoh lainnya adalah ß (U + 00DF LATIN SMALL LETTER SHARP S, atau escet ). Ini akan menjadi "SS" dalam huruf besar, meskipun sekarang ada bentuk huruf besar lainnya (ẞ, U + 1E9E LATIN CAPITAL LETTER SHARP S). Dan mengubah "SS" menjadi huruf kecil menghasilkan "ss", jadi (menggunakan terminologi Unicode untuk konversi huruf besar): toLowerCase (toUpperCase (ß))! = Ss.
Kasus bergantung pada lokal
Bahasa yang berbeda memiliki aturan konversi huruf yang berbeda. Contoh paling populer: i (U + 0069 LATIN SMALL LETTER I) dan I (U + 0049 LATIN CAPITAL LETTER I) dikonversi satu sama lain di sebagian besar lokal - sebagian besar, tetapi tidak semua. Dalam bahasa lokal az dan tr (bahasa Turki), huruf besar i adalah İ (U + 0130 LATIN CAPITAL LETTER I WITH DOT ABOVE), dan huruf kecil I akan menjadi ı (U + 0131 LATIN SMALL LETTER DOTLESS I). Terkadang, melakukannya dengan benar berarti perbedaan antara hidup dan mati.
Unicode sendiri tidak menangani semua kemungkinan aturan konversi kasus untuk semua lokal. Basis data Unicode hanya memiliki aturan umum untuk mengonversi semua karakter, tidak khusus untuk lokal. Juga ada aturan khusus untuk beberapa bahasa dan bentuk majemuk - bahasa Lituania, bahasa Turki, beberapa fitur bahasa Yunani. Yang lainnya tidak ada di sana. § 3.13 dari standar tersebut menyebutkan hal ini dan merekomendasikan pengenalan aturan terjemahan khusus lokal jika perlu.
Salah satu contohnya adalah tanda berbahasa Inggris - ini adalah kasus judul dari nama-nama tertentu. "O'brian" harus diubah menjadi "O'Brian" (bukan "O'brian"). Namun, dalam melakukan itu, "itu" harus diubah menjadi "Ini" dan bukan "Itu ADA". Contoh lain yang tidak ditangani dalam Unicode adalah kombinasi huruf Belanda "ij", yang, jika diubah menjadi kapitalisasi judul, harus diubah menjadi semua huruf besar jika muncul di awal kata. Karenanya, teluk terbesar di Belanda dalam daftar judul adalah "IJsselmeer" dan bukan "Ijsselmeer". Unicode memiliki karakter IJ U + 0132 LATIN CAPITAL LIGATURE IJ dan ij U + 0133 LATIN SMALL LIGATURE IJ jika Anda membutuhkannya. Secara default, konversi huruf mengonversinya satu sama lain (meskipun bentuk normalisasi Unicode yang menggunakan kesetaraan kompatibilitas akan membaginya menjadi dua karakter terpisah).
Kembali ke materi yang disajikan dalam laporan. Kompleksitas manajemen kasus Unicode berarti bahwa perbandingan tidak peka huruf besar / kecil tidak dapat dibuat menggunakan fungsi konversi huruf kecil atau huruf besar standar yang ditemukan di banyak bahasa pemrograman. Untuk perbandingan semacam itu, Unicode memiliki konsep pelipatan casing, dan §3.13 dari standar mendefinisikan fungsi toCaseFold dan isCaseFolded.
Anda mungkin berpikir bahwa mentransmisikan ke kotak terlipat mirip dengan mentransmisikan ke huruf kecil - tetapi sebenarnya tidak. Standar Unicode memperingatkan bahwa string kasus terlipat tidak harus huruf kecil. Sebagai contoh, bahasa Cherokee diberikan - di sana, dalam string yang dalam huruf besar, karakter dalam huruf besar juga akan ditemukan.
Dalam salah satu slide dalam pembicaraan saya, Laporan Teknis Unicode # 36 diimplementasikan sepenuhnya dengan Python. Normalisasi NFKC dilakukan dan kemudian metode casefold () (hanya tersedia di Python 3+) dipanggil untuk string yang dihasilkan. Dan meskipun demikian, beberapa kasus edge tidak berfungsi, dan ini sebenarnya bukan yang direkomendasikan untuk perbandingan ID. Kabar buruknya pertama: Python tidak mengekspos cukup properti Unicode untuk menyaring karakter yang tidak ada di XID_Start atau XID_Continue, atau karakter yang memiliki properti Default_Ignorable_Code_Point. Sejauh yang saya tahu, itu tidak mendukung pemetaan NFKC_Casefold. Juga tidak ada cara mudah untuk menggunakan NFKC UAX # 31§5.1 yang dimodifikasi.
Kabar baiknya adalah bahwa sebagian besar kasus tepi ini tidak melibatkan risiko keamanan nyata yang ditimbulkan oleh simbol yang dimaksud. Dan pelipatan casing pada prinsipnya tidak didefinisikan sebagai operasi pemeliharaan normalisasi (karenanya pemetaan NFKC_Casefold, yang dinormalisasi ulang ke NFC setelah pelipatan casing). Umumnya, saat membandingkan, Anda tidak peduli jika kedua string dinormalisasi setelah pemrosesan awal. Anda peduli jika preprocessing tidak tidak konsisten, dan jika itu menjamin bahwa hanya baris yang "seharusnya" berbeda setelahnya akan berbeda nantinya. Jika Anda mengkhawatirkan hal ini, Anda dapat melakukan normalisasi ulang secara manual setelah penambahan register.
Cukup untuk saat ini
Artikel ini, seperti laporan sebelumnya, tidak lengkap, dan hampir tidak mungkin untuk memasukkan semua materi ini ke dalam satu posting. Semoga ini menjadi gambaran umum yang berguna tentang kompleksitas topik ini, dan memberikan cukup titik awal untuk mencari informasi lebih lanjut. Karena itu, pada prinsipnya, Anda bisa berhenti di sini.
Bukankah naif untuk berharap bahwa orang lain akan berhenti menulis wahyu dari rangkaian "kesalahpahaman tentang X yang diyakini oleh programmer", dan sudah mulai menulis artikel seperti "kebenaran yang harus diketahui oleh programmer"?