Uvod u Unicode

Ako ste makar malo zbunjeni terminima kao što su "character set", "encoding", "code page", "Unicode", "UTF-8"... ovaj tekst će vam baciti malo svetla na celo to zamešateljstvo.

U suštini, kompjuteri ne znaju ništa o tekstu - oni razumeju samo brojeve. Ako želimo da iskoristimo kompjuter za baratanje tekstom, svakom slovu koje koristimo moramo dodeliti broj. Neformalno govoreći, character set je tabela koja pridružuje brojeve slovima i ostalim znakovima. Na primer, tabela za EBCDIC character set koji se koristi na IBM mainframe sistemima sadrži kolone za broj u heksadecimalnom i decimalnom zapisu i simbol koji je pridružen tom broju. Koristeći tabelu, lako možemo videti da se slovo A predstavlja brojem 193 (hex: C1). Takođe, možemo izvesti još dva interesantna zaključka: EBCDIC definiše brojeve samo za slova engleskog alfabeta; i EBCDIC koristi brojeve u opsegu 0-255 što znači da se svako slovo može predstaviti jednim 8-bitnim brojem.

Ostavimo EBCDIC koji je na računarima koje mi koristimo više istorijski kuriozitet i pogledajmo neke popularnije setove. ASCII je "tata" (ili preciznije, deda) savremenih character setova. Standardizovan je 60-ih godina prošlog veka i koristi 7 bitova (opseg brojeva 0-128) za svaki simbol. Naravno, i ovaj set ignoriše postojanje jezika različitih od engleskog, pa su se vremenom javile razne ekstenzije koje koriste još najmanje jedan bit da dodaju simbole iz drugih jezika. Među ovima su i Windows character sets, poznati i kao "Windows ANSI", kao i ISO 8859 setovi. Za azijske jezike koji imaju mnogo više slova, uvedeni su double-byte setovi (DBCS) gde su većina simbola 16-bitni brojevi. Shift JIS je primer jednog takvog seta i koristi se za japansko pismo. Za sve te ekstenzije je karakteristično da prvih 128 brojeva nasleđuju od ASCII seta, a ostatak popunjavaju nezavisno jedan od drugog.

Unicode je nastao iz potrebe da se svi ti šareni character setovi zamene jednim koji bi obuhvatao sva slova i simbole u svim jezicima. Projekat su započele firme Xerox i Apple u drugoj polovini 1980-ih, a 1991 je osnovan Unicode konzorcijum koji sačinjavaju značajne IT firme i u oktobru iste godine je usvojena verzija 1.0 Unicode standarda.

Prvih 256 (hex: FF) Unicode znakova odgovaraju ISO 8859-1 (Latin1) kodnom rasporedu, što omogućava laku konverziju dokumenata napisanih na zapadno-evropskim jezicima, a potom slede i ostali kodovi grupisani po alfabetima. U početku se smatralo da je dovoljno koristiti 16-bitne brojeve (0-FFFF, ili 0-65535) ali se vremenom ispostavilo da je broj znakova veći nego što se mislilo, pa danas Unicode pokriva opseg 0-10FFFF (0-1114111), dakle više od milion kodova, mada je u praksi najveći deo tog prostora danas nepokriven.

Sada nam je manje-više jasno šta je Unicode: uprošćeno rečeno, jedna ogromna tabela koja svakom simbolu pod kapom nebeskom dodeljuje jedinstven broj. A šta su onda UTF-8, UTF-16, UTF-32, i slične oznake koje se često javljaju zajedno sa pojmom "unicode"? Pogledajmo na primer kako bismo predstavili slovo "A" 8-bitnim ISO 8859-1 setom: kod je 41 heksadecimalno, ili 01000001 binarno - nema nikakve nedoumice. Pređimo sad na rane verzije Unicode standarda: dužina zapisa je 16 bita, i kod je 0041 heksadecimalno ili 0000000001000001 binarno. Kakva je sad ovde nedoumica? Kao što zna svako ko je pisao mrežne programe, na nekim hardverskim arhitekturama se 0041 predstavlja sa 00 41, a na drugim 41 00 (ma kako to izgledalo čudno na prvi pogled). Prvi sistem, gde značajniji bajt ide prvi, naziva se big endian, a drugi little endian. Tako već imamo dva načina za zapisivanje Unicode tekstova, čak i ako se držimo originalne 16-bitne dužine zapisa. A kako da program za obradu teksta zna koji je zapis u pitanju, kad npr čita tekst iz fajla? Da bi se odgovorilo na to pitanje, uveden je Byte Order Mark (BOM), oznaka koja se stavlja na početak fajla da odgovori na to pitanje. Za UTF-16, BOM ima vrednost FF FE za little endian (LE), a FE FF za big endian (BE). Takođe su uvedene oznake UTF-16LE i UTF-16BE za tekst za koji je unapred poznat "endianess" i u tom slučaju upotreba BOM-a nije dozvoljena.

Ne lezi vraže, to je samo početak priče: ispostavilo se da 16 bita nije uvek dobar izbor za zapis Unicode znakova. Sa jedne strane, takav zapis je praktično neupotrebljiv u svetu Unix-a koji je tradicionalno baziran na ASCII setu: na primer mnogi Unix alati tumače bajt koji ima vrednost 0 kao završetak stringa (ovo je konvencija programskog jezika C u kome su ti alati mahom napisani); u 16-bitnom Unicode setu ima dosta znakova koji sadrže 1 bajt koji je jednak nuli - na primer svi znakovi iz Latin-1 seta imaju "gornji" bajt jednak nuli kao što smo videli u primeru sa slovom 'A'. Rešenje za ovaj problem je smislio niko drugi do tvorac Unix-a Ken Thompson i njegov kolega Rob Pike za vreme jedne večere u Nju Džersiju: prvih 128 znakova (0000-007F) se predstavljaju jednim bajtom baš kao u ASCII setu, a ostali znakovi se predstavljaju sa više bajtova, prema šemi datoj u tabeli:
OPSEG ZNAKOVA BINARNI ZAPIS
U+0000→U+007F 0xxxxxxx
U+0080→U+07FF 110xxxxx 10xxxxxx
U+0800→U+FFFF 1110xxxx 10xxxxxx 10xxxxxx
U+10000→U+10FFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

Ludo zar ne? Ne samo da ASCII znakovi ostaju zapisani na bukvalno isti način, već je garantovano da se ne pojave kao neki od bajtova u više-bajtnim znakovima i tako zbune Unix alate. Ovaj metod zapisa je nazvan UTF-8 i ubrzo je postao dominantan u svetu Unix-olikih operativnih sistema, a i na Web-u.

Drugi nedostatak 16-bitnog zapisa je postao evidentan kad se ispostavilo da postoji potreba za kodiranjem većeg broja znakova nego što 16 bitni brojevi mogu da pokriju (FFFF, ili 65535). Tako su u UTF-16 zapis uvedeni tzv surogat parovi. Neki znakovi koji ne mogu da stanu u 16-bitni opseg se predstavljaju parovima brojeva. Takođe, uveden je i UTF-32, tj zapis koji koristi 32 bita za svaki znak. Recimo, slovo 'A' se predstavlja sa 00 00 00 41 (big endian). Već na prvi pogled se može zaključiti da ovaj način zapisa troši previše memorije na nule, pa u praksi nije preterano popularan.

Moderne verzije Windows operativong sistema (NT i naslednici) su bazirani na UTF-16 zapisu teksta. U stvari, svaki API poziv koji koristi ASCII ili druge setove, interno konvertuje tekst u Unicode. Shodno tome, i .NET stringovi su interno predstavljeni UTF-16 zapisom Unicode seta.

Za kraj, zabave radi, da vidimo kako bismo kodirali lepu reč "шницла" raznim kodnim zapisima:
ш н и ц л а
ISO-8859-5 E8 DD D8 E6 DB D0
UTF-16 0448 043D 0438 0446 043B 0430
UTF-8 D188 D0BD D0B8 D186 D0BB D0B0