utf8 string kezeles C-ben

Fórumok

Hi.

Van egy ilyen progim:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <locale.h>

int main()
{
char *utf = "ancients ▒. Flavius";
// ^^ itt egy ×-jel van utf8-ben kodolva
printf("%d, %s\n", strlen(utf), utf);
return 0;
}

namost ennek az eredmenye sarge-n (nem utf konzolon):
$ ./ize
19, ancients Ă Flavius
$
Ugyebar a "ancients × Flavius" csak 18 betu hosszu, az strlen azt mondja hogy 19. :(

Akkor probaljuk mashogy:
$ locale -a|grep utf
en_US.utf8
hu_HU.utf8
$ export LANG=hu_HU.utf8
$ export LC_ALL=hu_HU.utf8
$ ./ize
19, ancients Ă Flavius
$

Eredmeny ugyanaz. :(

Hozzászólások

A printf azt a bytesorozatot írja ki változatlanul, ami a forrásban áll. Senkit nem érdekel, mi a locale, sem pedig a kiírandó sztring kódolása. Byte-ról byte-ra változatlanul írja ki. (A char típus elnevezése roppant szerencsétlen C-ben, byte-nak kellene hívni.) Hasonlóan a strlen is a byte-okat számolja.

Ha utf8-ban ábrázolod belül a sztringet, akkor nem tudok ésszerű megoldást erre. Írni kell saját függvényt, ami egy utf8 sztring első 5 karakterét írja ki, vagy visszaadja az első 5 karakter végének offsetét. Egyébként a sztori még bonyolultabb, hiszen jó kérdés, hogy miért akarod 5 karakternyire levágni. Ha azért, hogy a terminálban 5 oszlopba kiférjen, akkor jócskán bonyolít a helyzeten, hogy lehetnek dupla széles, illetve nulla széles karakterek is, tehát nem biztos, hogy 5 db utf8 karakter kiírása 5-tel viszi arrébb a kurzort. Ha mindenképp 5 utf8 karaktert akarsz kiírni, akkor könnyebb a dolgod. Ha feltételezhető, hogy a sztring érvényes utf8 kódolású, akkor egyszerűen meg kell keresned benne a hatodik 1-127 vagy 192-255 értékű byte-ot, és az előttig kiírni.

Másik lehetőség: ha belül nem utf8, hanem ucs4 ábrázolást használsz a wchar típus segítségével, a string konstanst például L"valami" formában megadva, akkor a wprintf szerintem jó lesz neked, bár én még nem használtam ezt a függvényt. Az szinte tuti, hogy a wprintf előtt megfelelő setlocale parancsra szükséged lesz. Ilyenkor viszont az a cinkesebb, hogy a forrásba hogyan tudsz ékezeteket megadni az L"" konstansban. A gcc a 3.4-estől kezdve támogatja (azt hiszem), hogy megadd a forráskód karakterkészletét. Ez ugyebár nem túlzottan hordozható megoldás.

egy progi logja utf8 kodolasu (van benne mindenfele sallang), azt megkapja ez a progi, feldolgozza, es olvashato formaban egy irc csatira tovabbitja. a sprintf azert kell, hogy szep formazott legyen.

--
A vegtelen ciklus is vegeter egyszer, csak kelloen eros hardver kell hozza!

Aha, tehát arra nem lesz szükség, csak most példaként írtad, hogy a forrásban utf8 sztringkonstansok álljanak. Bár végülis mindegy.
Az irc csatira utf8-ban kell továbbítanod, vagy más kódolásban? Ha másban, akkor úgysem úszod meg az átalakítást, és a pl. latin2-re átalakított verziót már egyszerűbb formázni. Ha utf8 kell, akkor marad vagy a kézzel-lábbal formázgatás (esetleg a glib lehet hogy tartalmaz okos segédfüggvényeket ehhez), vagy pedig a wchar-ra alakítás és a wsprintf.

Hoppá! Álljunk meg! Most hirtelen beugrott valami olyasmi, hogy mintha a sima printf() is figyelembe venné a locale-t a precíziós formázó dolgoknál. Igen, volt, hogy valami sima program bináris adattal azért szállt el a printf-en, mert fix szélességre volt formázva, és utf8 környezetben a nem utf8 sztringgel a printf ezzel nem tudott mit kezdeni. Szóval most erősen úgy dereng nekem (de ellenőrizd le), hogy a sima printf is tud adott szélességben kiírni utf8-at. Az biztos, hogy előtte a locale-t a setlocale() függvénnyel inicializálnod kell valami utf8-asra.

glib-ben vannak ilyen okosságok:
http://developer.gnome.org/doc/API/2.0/glib/glib-Unicode-Manipulation.h…

Például az első 5 betű kiírásához azt mondhatod, hogy
sztring = "valami_utf8";
kiirando_byteok = g_utf8_offset_to_pointer(sztring, 5) - sztring;
printf("%.*s", kiirando_byteok, sztring);

na egyelore ennyi van:

#include 
#include
#include
#include
#include

int main()
{
if (setlocale(LC_CTYPE, "hu_HU.utf8")==NULL) {
fprintf(stderr, "hiba van bazze");
return 1;
}

char *utf = "ancients Ă. Flavius";
// ^^
wchar_t tmp[256] = {L'\0'};

mbstowcs(tmp, utf, 128);
wprintf(L"~%20ls~\n", tmp);
wprintf(L"~%20ls~\n", "ize");
return 0;
}

kimenet:
$ ./ize
~ ancients × Flavius~
~ ize~
$
(portal levagja a spacekat... :(

de asszem egyelore marad az iconv isoakarhany, aztan ha lesz idom, akkor megcsinalom rendesen...
mindenesetre thx the help
--
A vegtelen ciklus is vegeter egyszer, csak kelloen eros hardver kell hozza!

Ez így jónak tűnik nekem... Pár megjegyzés:
- "ize" helyett L"ize" kell, különben szemetet ír ki
- #define _GNU_SOURCE, vagy -std=c99 kapcsoló a gcc-nek, különben -Wall esetén warningol hogy wprintf nincs deklarálva
- hu_HU.UTF-8 a javasolt pontos név, a glibc-nek a hu_HU.utf8 is jó, de az X-nek nem, jobb rászokni a precíz alakra.

Végülis a fenti programban történik egy oda-vissza konverzió. Ha jól értem, a forráskódban egy utf8 kódolású × jel áll. Ha utf8 locale-t adsz meg neki, akkor átalakítja utf8-ról ucs4-re (wchar), majd a wprintf a kiíráskor ezt visszaalakítja utf8-ra. Két space-t rak elé, tehát a hosszát helyesen 18-nak számolja. Azt hiszem, neked pont ez kell.

Ha latin2 locale-t adsz meg a setlocale()-nek, akkor is pontosan az utf nevű sztringben definiált bytesorozatot írja ki, de előszőr latin2-ről wcs-re alakítja, emiatt a hossza 19 karakter lesz, így csak egy space-t rak ki, majd a wprintf visszaalakítja latin2-re, így visszakapod azt a 2 byte-ot, ami utf8-ban egy × jellé áll össze.

Vagyis szerintem tök jó a progi és lényegében azt csinálja, ami neked kell.

Ugyanilyen gondjaim voltak php-ban is. Ott a "multibyte string functions" volt a megoldás. Talán ez segít: gugli

Se pénz, Se posztó, Sepultura

//ez a program UTF-8 kódolással van írva
#include
int main()
{
printf("Kázmér füstölgő fűnyírót húz.\n");
return 0;
}

export LANG=en_US.ISO-8859-1
export LANG=hu_HU.UTF-8

A LANG beállítástól függetlenül a program mindig ugyanazt
a bytesorozatot írja ki. Attól függően azonban, hogy az
xterm-ben milyen LANG beállítás van, ugyanaz a bytesorozat
szemétnek, vagy normális szövegnek __látszik__.

Megjegyzés-0:
Az UTF-8 __változó__ bytehosszúságú kódolás,
a karakterek 1-6 byte hosszúságú bytesorozatokkal
vannak kódolva, az strlen a byteok számát adja.

A másik lehetőség a Unicode használata:

//ez a program is UTF-8 kódolással van írva
#include
#include
#include
int main()
{
setlocale(LC_CTYPE,"");
wchar_t *p=L"Kázmér füstölgő fűnyírót húz.\n";
wprintf(L"%ls",p);
return 0;
}

Megjegyzés-1:
A gcc tudja ezt, az MSC viszont nem tudja
az UTF-8 kódból előállítani az UCS kódokat,
azért az MSC-nek ezt kell írni:

wchar_t *p=L"K\u00e1zm\u00e9r f\u00fcst\u00f6lg\u0151 f\u0171ny\u00edr\u00f3t h\u00faz.\n";

Megjegyzés-2:
Ezt viszont a gcc 2.95 nem tudja.

Megjegyzés-3:
Az L"..." wide stringek wchar_t típusok sorozatából állnak,
a wchar_t a mostani fordítókban 32 bit széles.

Sőt, még a gcc 3.3 sem tudja, csak a 3.4 és annál újabbak. És valami kapcsolóban meg kell adni a forrás karakterkészletét, ezt az információt tudtommal nem lehet a .c fájlba belerakni. Tehát rögtön ott tartunk, hogy a forrás csak megfelelő Makefile-lal vagy egyéb módon rögzített fordítási szabályokkal együtt jó. Ez persze nem feltétlenül baj, az adott szitun múlik. Én még nem láttam olyan kódot, ami erre támaszkodott volna.

Már csak azért is ritka az ilyen, mert forráskód normális esetben nem igazán tartalmaz nem-ASCII karaktereket, hiszen kulturált program esetén a forrásban csak angol sztringek szerepelnek, és a fordítás a gettext-től érkezik. Jó, angol szövegben is lehet ékezetes betű, copyright jel stb., de a gettext char*-gal dolgozik, nem wchar*-gal, így simán be kell rakni az utf8-as angol szöveget a forrásba char*-ként, ez minden c-fordítónak jó lesz. Általában sokkal egyszerűbb ez a megközelítés. Csak pont azt szívás megcsinálni, hogy egy sztring első 5 karakterét írjuk ki, ami az eredeti felvetés volt. Erre a problémára épp a wchar típus és a w* függvények a nyerők valóban.

Ja, kódgenerátor esetén valszeg célszerű ilyet használni. Egyébként hogy adod meg a BMP-n túli karaktereket? Nyilván arra is van valami mód, de nyilván ez a jelölés nem kiterjeszthető, mert \u00012345-ből nem derül ki, hogy az 1-es Unicode karakter után literális 2345 áll, vagy pedig a 12345-ös Unicode-ról van szó.

Egyébként meg bizonyos értelemben mindenféle ilyesmi escape-eléstől borsódzik a hátam. Még ha kódgenerátor csinálja, akkor is, hadd nézzek bele, hadd lássak valami értelmeset. Az egész Unicode-os történetnek én úgy látom, az lényege, hogy mindenütt a helyes sztringet lássam, és én személy szerint ezt elvárom nemcsak a végeredmény szoftver működésétől, hanem a fejlesztés minden mozzanatától is. Szóval ami engem illet, ha csak lehet, kerülöm az ilyen olvashatatlan megadásokat.