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. (Kódolással kapcsolatos részletek az http://infoc.eet.bme.hu/karakterkodolas.php 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)

    Konzol ablak betűkészletének változtatása: ablak fejlécén jobb egérgomb -> Tulajdonságok -> Betűtípus
  1. 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.
  2. 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.
  3. 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!

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.

Utolsó frissítés: 2018-04-13 22.43