Magyar ékezetes szöveg kezelése
A különféle nemzeti karakterek kezelése sajnos nem triviális feladat konzolos C/C++ programból. Ez az összefoglaló megpróbál rámutatni az okokra, és megpróbál korlátozott megoldásokat is adni. (Karakterkódolással kapcsolatos részletek az http://infoc.eet.bme.hu/karakterkodolas oldalon találhatók.)
Történelmi okok
A probléma alapja még a 60-as évekre nyúlik vissza, amikor a legtöbb gyártó elfogadta az ASCII kódolást, amely 7 biten kódolja a karaktereket – ez 128 különféle jel leírását teszi lehetővé. A különböző nemzeti karakterek ebbe nem fértek be. Ezért elkezdték a 8. bitet is felhasználni, ami az adatátvitel során a paritásbit szerepét töltötte be. Sajnos a kódok kiosztása szinte gyártónként változott és egyes nemzeti karakterek még a 256 lehetőségbe sem fértek be. A 80-es éveben úgy tűnt, hogy 16 vagy 32 biten biztosan megoldható a dolog, ezért pl. a A C90-ben bevezették a wchar_t típust ami 16 biten kódolja a karaktereket. A wchar_t önmagában karaktereket sem és a kódolást sem határozza meg. A különböző karaktereket, szimbólumokat a Universal Character Set (UCS) határozza meg, amit az ISO/IEC 10646 jelű szabvány ír le. Ennek gondozását, folyamatos fejlesztését a Unicode konzorcium látja el. Kezdetben úgy tűnt, hogy 16 biten minden elfér, így a kódolás abból állt, hogy az UCS minden karakteréhez hozzárendeltek egy egész számot 0 és 65535 között. Így a wchar_t bevezetése jó választásnak látszott. Azonban a bájtsorrendből adódó problémák és az ASCII kódokkal való kompatibilitás hiánya miatt ez a kódolás csak korlátozottan terjedt el. Az UCS jelenleg több mint 1.1 millió jelet tartalmaz, melyet számos módon kódolnak. Manapság egyik leggyakrabban alkalmazott kódolás az UTF-8, ami változó bitszélességű és visszafelé kompatibilis az ASCII kódolással.
Ékezetes karakterek konzolos C/C++ programokból
Annak ellenére, hogy az UTF-8 számos előnnyel rendelkezik és különösen elterjedt webes környezetben, C/C++ programokból ISO-8859-2 (latin2), esetleg ISO-8859-1 (latin1) kódolás használatát javaslom. Kezdő lépésként mindenképpen. Az UTF-8 változó bitszélessége ugyanis a helyfoglalásban, tárolásban bonyodalmat okoz, és közvetlen támogatottsága csak a C++11-ben van. A latin2 és a latin1 kódolás 1 byte-on tárol (Latin1-ben kalapos a hosszú ő és ű).
C/C++ programok a nyelvi környezetének beállítására a szabványos setlocale()
függvényhívás szolgál. Ezzel kell/lehet beállítani a karakterek osztályozását, dátumok és pénznemek megjelenítését, valamint a mértékegységek használatát is befolyásoló globális (a programra nézve) környezetet. Számunkra ezek közül a legfontosabb a karakterek osztályozására (isalpha(), is...
), és a stringek összehasonlítására strcoll()
gyakorolt hatás.
A továbbiakban egy kis C++ kód segítségével mutatom be a lehetőségeket. A kód a nyelvi környezet beállítására a setlocale függvényt használja. C++-ban erre van egy külön objektum (locale::globale), de ez csak bonyolítaná a példámat.
Példaprogram (lang.cpp)
#include <iostream> #include <string> #include <locale> #include <fstream> using namespace std; int main() { string s; setlocale(LC_ALL, ""); // ez beállítja az op.rendszer által használt // nyelvi környezetet, amit feltételezünk, hogy magyar // Windows alatt a konzol ablak betűkészletét állítsa "Lucida Console"-ra! cout << "árviztűrő tükörfúrógép" << endl; // szöveg1 cout << "TÜSKÉSHÁTÚ KÍGYÓBŰVÖLŐ" << endl; // szöveg2 ifstream inf("lang.cpp"); // beolvassa saját magát while (getline(inf, s)) cout << s << endl; #if defined(WIN32) || defined(_WIN32) || defined(WIN64) || defined(_WIN64) getline(cin, s); // működik a konzolos beolvasás is ? cout << s << endl; /// Windows alatt egyéb varázslatra is szükség van ... SetConsoleCP(1250); SetConsoleOutputCP(1250); #endif while (getline(cin, s)) // és most? cout << s << endl; }
Fordítsuk le és futtassuk a fenti kis példaprogramot! Ellenőrizzük, hogy az ékezetes karakterek jól látszanak-e! Ellenőrizzük beírt szöveggel is!
Környezetek (op. rendszerek)
- Windows7 (HSZK)
A Windows-os környezetre vonatkozóan alapvetően a HSZK környezetében szerzett tapasztalataimat osztom meg. Tudományos magyarázatot nem próbálok hozzá adni. Fontos, hogy a konzol ablakban olyan betűkészletet legyen kiválasztva, ami meg tudja jeleníteni a magyar ékezetes karaktereket. Ilyen pl. a Lucida Console betűkészlet.- Visual Studio 2008
A VS-ben beállítható (File->Advanced Aave Options), hogy milyen kódolásban legyen elmentve a forrás (UTF8, UTF16, Windows 1250 ~ latin2), de a futtatható kódba (exe) mindig win1250-ben tette a szövegkonstansokat. Ez bizarrul hangzik, de így történik. Elképzelhető, hogy ez valahol konfigurálható, vagy attól függ, hogy a mi az alapértelmezés a Windows-ban.
A setlocale(LC_ALL,"") függvényhívás után a program jól írt ki, a konzol ablakból beolvasott ékezetes karakterek azonban rosszul jelennek meg. Úgy tűnik, mintha a konzol 852-ben maradna. (Czirkos Zoltán által készített fent idézett anyag felfedi a rejtélyt: külön kell állítani az input és az output kódlapot. Úgy tűnik a setlocal függvényt megvalósító programozók ezt nem tudták, ahogy én sem).
A programocska Windows 1250-es/latin2 kódolású fájlból jól olvas be. - CodeBloks 10.x
Az editor beállításainál megadott kódolással kerültek be a szövegkonstansok a kódba. Latin2 választása után lefordítva a kódot a setlocale(LC_ALL,"") függvényhívás után a program jól írt ki, a konzolról beolvasott ékezetes karakterek azonban rosszul jelennek meg. Latin2 kódolású fájlból a program jól olvas be.
- Visual Studio 2008
- Windows XP
Windows 7-tel megegyező tapasztalatok, amennyiben a registry-ben a HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Nls\CodePage\OEMCP = 1250-re van állítva. - Linux/UNIX
- Ellenőrizzük, hogy milyen nyelvi környezet van telepítve és beállítva. A LANG nevű környezeti beállítás meghatározza az idő, a pénznem, valamint az ábécébe rendezés módját is. Az echo $LANG paranccsal lehet kiíratni a pillanatnyi beállítást. Disztribúciótól, beállítástól függően ilyen eredménye lehet parancsnak:
- C - alapbeállítás, angol ANSI kódolás
- en_US.utf8 - amerikai angol könyezet, UTF-8 kódolás
- hu_HU.utf8 - magyar környezet, UTF-8 kódolás
- hu_HU.iso88592 - magyar környezet, latin2 kódolás
- hu_HU.iso88591 - magyar környezet, latin1 kódolás
- hu_HU - magyar környezet, feltehetően latin2 kódolás
Azt, hogy milyen nyelvi környezeteket támogat az adott rendszer a locale -a paranccsal kérdezhetjük le.
-
Ha nem latin2 vagy latin1 kódolás van beállítva akkor állítsuk azt át pl. a következő paranccsal:
export LANG=hu_HU.iso88592
A beállítás sikerét a locale paranccsal ellenőrizhetjük. Ha tényleg elérhető az adott nyelvi környezet, akkor minden sorban a beállított érték fog látszani (kivéve az LC_ALL) pl:
LANG=hu_HU.iso88592 LC_CTYPE="hu_HU.iso88592" LC_NUMERIC="hu_HU.iso88592" LC_TIME="hu_HU.iso88592" LC_COLLATE="hu_HU.iso88592" LC_MONETARY="hu_HU.iso88592" LC_MESSAGES="hu_HU.iso88592" LC_PAPER="hu_HU.iso88592" LC_NAME="hu_HU.iso88592" LC_ADDRESS="hu_HU.iso88592" LC_TELEPHONE="hu_HU.iso88592" LC_MEASUREMENT="hu_HU.iso88592" LC_IDENTIFICATION="hu_HU.iso88592" LC_ALL=
- Vigyük be, fordítsuk le, és futtassuk a példaprogramot!
- Fontos, hogy más a szövegbevitelnél jól legyen beállítva a LANG környezeti változó és a terminálemulátorunk (pl. putty) is latin2 kódolással működjön!
- Ellenőrizzük, hogy milyen nyelvi környezet van telepítve és beállítva. A LANG nevű környezeti beállítás meghatározza az idő, a pénznem, valamint az ábécébe rendezés módját is. Az echo $LANG paranccsal lehet kiíratni a pillanatnyi beállítást. Disztribúciótól, beállítástól függően ilyen eredménye lehet parancsnak:
Karakterek osztályozása, stringek hasonlítása
Amennyiben sikerül jól beállítani a nyelvi környezetet, akkor bátran használhatjuk a karakter osztályozó és összehasonlító függvényeket. Erre mutat példát a http://svn.iit.bme.hu/proga2/locale könyvtárban található C példaprogram.
Amikor a jó nyelvi beállítás bosszantó lehet
Általában a helyes nyelvi beállítások segíteni szokták a felhasználókat. Eddig egy esetben találkoztam nagyon idegesítő mellékhatással: Amennyiben a LANG környezeti változó nem C-re, vagy nem POSIX-ra van állítva, akkor a bash nem különbözteti meg a fájlnév helyettesítéskor a kis- és nagybetűket. Ezzel a rendkívül érthetetlen és bosszantó feature-rel bárki pillanatok alatt akaratlanul kitörölheti az összes fájlját. (pl: rm [A-Z]*), holott csak a nagybetűvel kezdődőeket szerette volna. A bash manuál ezt a működést gondosan megmagyarázza, de nekem nagyon nem szimpatikus. Más shell-ek esetén ilyen hatást nem tapasztaltam.