Qt4: QString to char*, keeping special characters

 ( greenvirag | 2009. február 1., vasárnap - 14:16 )

Szisztok!

Egy eleg bosszanto problemahoz erkeztem, de valahogy megsem sikerult megtalalnom a megoldast.
A kerdes roppant egyszeru:

Van mondjuk egy QLineEdit-em, abba a user gepel valamit, ekezetes betuket hasznalva. Lekerni ennek tartalmat konnyen megy, a kapott ertek tipusa QString. Hogyan tudom ezt atkonvertalni anelkul char*-ra, hogy az ekezetek elvesznenek?

A Qt defaulton utf8-at hasznal, rendben van. Eleg sok mindent kiprobaltam mar, pl: QString: toAscii, toLatin1, toLocal8Bit, vagy siman toStdString es annak a c.str()-je. A konvertalgatasok nem jartak tul sok sikerrel, bar mintha egyszer valamilyen verzioban a cout szepen irta volna ki a betuket, de vele parhuzamosan a qDebug() tovabbra sem.

Eleg kezenfekvo a szituacio, gondolom masok is talalkoztak mar vele.
Mi a megoldas?

Koszi elore is.

Hozzászólás megjelenítési lehetőségek

A választott hozzászólás megjelenítési mód a „Beállítás” gombbal rögzíthető.

Nem értem, h hol a probléma. Amikor a QString-et char*-á konvertálod, az ékezetek a megadott (vagy default utf8) kódolás szerint továbbra is benne lesznek. Arra kell odafigyelni, hogy ahol ezután ezt a char*-ot használod, az tudja helyesen kezelni/megjeleníteni a benne lévő latin/utf/stb. karaktereket.

--
The Net is indeed vast and infinite...
http://gablog.eu

Nem egeszen ertem hogy ez hogy mukodne. Ha ellenorzeskepp pl ki akarnam iratni couttal vagy qDebuggal a char* valtozom tartalmat, akkor hogy kene felszolitanom oket az utf8 helyes kezelesere?

Az csak kinyomja a képernyő byte-streamére a char* bytejait. A konzolodnak kell megfelelő kódolásban megjeleníteni.
Vagyis arra kell figyelni, hogy a megjelenítő (akár konzol akár GUI) ugyanolyan karakter-kódolást használjon mint amilyen kódolásban a streamre kerülő char* .

--
The Net is indeed vast and infinite...
http://gablog.eu

Na jo, most megint tesztelgettem sokfelekepp.
A konvertalas utan a cout tokeletes, de a qDebug() nem.

	QString qs = "űúáűáőéűőtfd";
	char* cs = (char*) "űúáűáőéűőtfd";

	std::cout << "1 COUT " << qs.toStdString() << " " << cs << std::endl;
	qDebug() << "1 QT" << qs << " " << cs;

	char* cs2 = qs.toAscii().data();

	std::cout << "2 COUT " << qs.toStdString() << " " << cs2 << std::endl;
	qDebug() << "2 QT" << qs << " " << cs2;

	char* cs3 = (char*)qs.toStdString().c_str();
	std::cout << "3 COUT " << qs.toStdString() << " " << cs3 << std::endl;
	qDebug() << "3 QT" << qs << qs.toAscii() << qs.toLatin1() << qs.toLocal8Bit();

	QString qs2 = QString().fromAscii(cs3,strlen(cs3+1));

	std::cout << "4 COUT " << qs2.toStdString() << " " << cs3 << std::endl;
	qDebug() << "4 QT" << qs2 << " " << cs3;

kod a kovetkezoket eredmenyezi:

1 COUT űúáűáőéűőtfd űúáűáőéűőtfd
2 COUT űúáűáőéűőtfd űúáűáőéűőtfd
3 COUT űúáűáőéűőtfd űúáűáőéűőtfd
4 COUT R˜	! R˜	! 
1 QT "űúáűáőéűőtfd"   űúáűáőéűőtfd 
2 QT "űúáűáőéűőtfd"   űúáűáőéűőtfd 
3 QT "űúáűáőéűőtfd" "űúáűáőéűőtfd" "űúáűáőéűőtfd" "űúáűáőéűőtfd" 
4 QT "R˜	À¡"    

Azt hiszem nem egeszen ertem, miert.

A probléma az, hogy a QString mindig Unicode-ban (azt hiszem, hogy UTF-16 -ban) tárolja a karaktereket, a C++ fájlban pedig valamilyen 8-bites kódlap szerint értelmezi a fordító a karaktereket (nem UTF-8).

Nálam így lett jó kiírás:
char* cs = (char*) "űúáűáőéűőtfd";
qs=QString::fromLocal8Bit(cs);
const char *cs2=qs.toLocal8Bit();
qDebug()<

Amennyiben ez a gond, akkor a QApplication után kiadott

QTextCodec::setCodecForCStrings(QTextCodec::codecForName("UTF-8"));

segít. És akkor nem kell a trükközés.

"...handing C++ to the average programmer seems roughly comparable to handing a loaded .45 to a chimpanzee."
-- Ted Ts'o

C++ forrásfájlba nem biztos, hogy illik ékezetes karaktereket berakni.

Nem szoktak ilyet csinálni, legalábbis ezt még sehol sem láttam.

Nagyon sok "jobban tudom mit csináljak" editor van, ami kérdés nélkül konvertál és ha elmented ilyenkor a fájlt, következő indításnál már eleve rossz lesz. A normális szerintem az, ha a C++ forráskód karakterkódolás független.

Mindent angolul és i18n(...)-t raksz elé, aztán kbabel-lel lefordítod.

Az i18n már fel van készítve arra, hogy rendesen mindent megegyen, mert a fordító által alkalmazott kódolás bele van égetve a nyelvi fájlokba.

Amikor Linux alatt egyik disztribúció ISO-8859-2 kódolást használt, a másik UTF-8-at, akkor ugyanaz a Krusader bináris mindkettőn elfutott. Ha a forráskódba égettük volna a karakterkódolást, egyesével minden C++ fájlt konvertálni kellett volna.

Normális editornál be lehet állítani a forrásfile kódolását.
Ha mégsem, akkor is max rosszul jeleníti meg, de eltűntetni nem fogja az ékezeteket.

Az Utf-8-as stringeket a C++ fordító megeszi (mint sima string literált). A gond az, hogy a QString konstruktora nem tudja, hogy a kapott karaktersotozat milyen kódolású.
Ezen segít ez.
Vagy használhatod a fromUtf8-at. A fromLocal8Bit ebben az esetben csak véletlenül működik.

I18n-hez Qt-ban a tr(...)-t illik használni.

"...handing C++ to the average programmer seems roughly comparable to handing a loaded .45 to a chimpanzee."
-- Ted Ts'o

A toLocal8Bit/fromLocal8Bit a helyi karaktertáblának megfelelő kódolást alkalmazza (környezeti változókban ami meg van adva).

Nálam más nem működne. Ha fixen tudod, hogy UTF-8 jön be, akkor értelemszerűen azt kell használni.

Mivel én fájlrendszerkezelőt írok, halvány lila fogalmam sincs, hogy az adott Linux disztribúció milyen enkódolást használ. Ezért kell a toLocal8Bit()-et használni.

A forrásban lévő stringekről beszéltem. Annak a kódolását meg azért szokta az ember tudni. :)

"...handing C++ to the average programmer seems roughly comparable to handing a loaded .45 to a chimpanzee."
-- Ted Ts'o

Igazabol nem is az volt a gondom, hogy a forraskodba akartam irni ilyesmit.
Amit szerettem volna: user beir vmit textedit/lineedit mezobe, majd ennek tartalmat lekerdezem, es a kapott QString-et ugy konvertalom char*-ba, hogy az ekezetek szepen megmaradjanak.

Nem volt idom azota tesztelni, de ma teszek meg egy probat.

Varj egy kicsit!

A char*-ban minden egyes char 8 bit informaciot tarol el. Azaz nem mas ez, mint egy bytesorozat.

Azonban ha a usernek megengeded, hogy monjuk Unicode karaktereket irjon be a texteditbe, akkor kezdodnek a problemak. A QStringtol lekert char* bytesorozat milyen kodolas szerint fogja tartalmazni a karaktereket? UTF-8, UTF-16, UCS-2,stb? Ezeknel a kodoknal minden karaktar eltero szamu bytera kepezodik le (kiveve az UCS-4, aka UTF-32), igy olyat soha nem fogsz tudni megoldani, hogy pontosan annyi darab byte-on reprezentald a felhasznalo altal beadott karaktersorozatot, ahany karakterbol az all.
Termeszetesen a beadott karaktersorozat bytesorozat lekepezeset kiirathatod a terminalra, ekkor a terminal beallitasaitol fugg, hogy a kapott bytesorozatott milyen kodolas szerint ertelmezi es jeleniti meg. Azaz ha te egy olyan char*-ot ( ne feledjuk, bytesorozat, nem Unicode karakterek sorozata, az inkabb a wchar*) adsz at a terminalnak, ami UTF-8 kodolasban tartalmazza a karaktereidet, de azt a terminal mondjuk ISO-8859-2-kent ertelmezi, akkor ket dolog tortent:
1. az ekezetek szepen megmaradtak (a bytesorozattal minden rendben)
2. a terminal ezeket rosszu ertelmezte

Szoval pontosan mit is szeretnel?

A QString::toLocal8Bit()-nek van egy erdekessege: The returned byte array is undefined if the string contains characters not supported by the local 8-bit encoding.

Esetleg hasznald a QString::toUtf8()-at, ez igy ugyanugy bytetombot ad vissza mint a tobbi, csak itt biztos, hogy minden, a felhasznalo altal megadott karakter reprezentalhato a tomb egy vagy tobb egymast koveto elemekent.

Masik lehetoseg a Qtring::toStdWString() hasznalata, ez std::wstring-gel ter vissza, amely UTF-16 kodolasu abban az esetben, ha wchar_t 2 byteos, es UCS-4, ha wchar_t 4 byteos. Itt sem lesz gondod az ekezetekkel.

A QString::toStdString()-gel a kovetkezo a baj:
If the QString contains non-ASCII Unicode characters, using this operator can lead to loss of information.

A magyar ekezetek sajnos egyike sem ASCII karakter, es neked ez a bajod.

Ezen nem kell ennyire aggódni.

toLocal8Bit()-ot írj ki a konzolra. Ha az pont utf-8-as, akkor ok.
Ha ISO-8859-2, akkor a megfelelő karakterek látszanak. Az olyan karakterek, amik az adott kódolásban nincsenek, azok meg ígyse-úgyse lesznek rendesen kiírva.

A toUtf8 csak akkor jó, ha a konzol is utf-8-as, minden más esetben még akkor is rossz karaktereket fog kiírni a nem ASCII karakterekre, ha azok egyébként benne lennének a codepage-ben.

"...handing C++ to the average programmer seems roughly comparable to handing a loaded .45 to a chimpanzee."
-- Ted Ts'o

De o nem akart kiirni konzolra. Hanem char*-kent szeretne a QStringjet viszontlatni.

Ő QDebug-gal akar kiírni a konzolra...

"...handing C++ to the average programmer seems roughly comparable to handing a loaded .45 to a chimpanzee."
-- Ted Ts'o

A kiiratas csak egy ellenorzes volt, hogy akkor hogy is neznek ki a dolgok.
Amit szerettem volna eloszor, az tenyleg csak annyi, hogy QStringbol char*-ba konvertalas az ekezetek megtartasaval.

Igen, itt ez az, amit nem értettünk.

A QString -> char* konvertálás nem 1-1 értelmű.
A végeredmény attól függ, hogy milyen karakterkódolást használsz.

Ha Windows-zal kommunikálsz, asszem ISO-8859-2 kell, a jelenlegi Linuxok meg általában UTF-8-cal mennek.

A megfelelő karakterkódolást a fogadó rész dönti el. A kérdés az, ahogy akinek a char*-ot küldöd, hogyan értelmezi azt.

A QString(UTF-16) azért lett bevezetve, hogy véget vessenek a karakterkódolási pokolnak és valami egységeset kialakítsanak.

Az UTF-8 nagyon hasonlít az UTF-16-hoz, csak a 0-127 karaktereket 1 byte-on ábrázolja.

"A QString(UTF-16) azért lett bevezetve, hogy véget vessenek a karakterkódolási pokolnak és valami egységeset kialakítsanak.

Az UTF-8 nagyon hasonlít az UTF-16-hoz, csak a 0-127 karaktereket 1 byte-on ábrázolja."

Akkor menjünk sorban.
Kezdetben vala az ASCII, eredetileg 7 biten tárolta a betűket, és egyéb jeleket. Mivel ebből 1 byte lett, 128-as érték fölötti szabad helyeket elkezdték másra használni. Ebből lettek a codepage-ek.
A magyar karakterek a Latin-2(ISO-8859-2)-be kerültek bele.

Persze lassan kiderült, hogy ez nem a legjobb megoldás, Megalkották az Unicode-ot, ami 32 biten (!) tárolja a karaktereket. (0-127 megegyezik az ASCII-vel, 0-65535-et Basic Multilingual Plane-nek (BMP) hívják, és kb mindent tartalmaz, amivel földi halandó találkozhat).

UTF-8: ASCII karakterek 1 byte-ot, ami fölötte van, az 2-4 byte-ot foglal.

UTF-16: BMP 2 byte, minden fölötte 4 byte.

A QString UTF-16-ot használ. Ebből következik, hogy ha neked char* kimenet kell, és nem akarsz információt veszteni, akkor UTF-8-ra kell konvertálnod. Amit a konzolon vagy látsz, vagy nem...

"...handing C++ to the average programmer seems roughly comparable to handing a loaded .45 to a chimpanzee."
-- Ted Ts'o

A Windows alapertelmezetten a belso kommunikacio soran (API hivasok) (NT,2k,XP, Vista) Unicode karakterkeszlettel dolgoznak, UCS-2 kodolasban. Szep lenne, ha a Windows ISO-8859-2-t hasznalna, akkor nem lehetne japan, kinai meg orosz, arab, stb. Windows verzio.
A kulonbozo Linux API-k is mas-mas kodolasu karaktersorozatokat fogadnak el, bar egyre novekvo azoknak a szama, amelyek UTF-8-at tamogatnak. A kulonfele terminalok esetben meg joesetben beallithato, hogy a programok kimenetet milyen kodolas szerint ertelmezzek.
Mint ahogy sok program esetben beallithato, hogy a kimenete milyen kodolasu legyen.
Igy elegge eros altalanositas, hogy a Linuxok UTF-8-cal mennek, nagyon sok Linuxon futo program elegge gyengen tamogatja az UTF-8-at, lasd pl. mc hivatalos verzioja.

Majdnem. Minden API hivasnak van unicode meg nem unicode verzioja is, am hogy mi fogad el unicode stringeket es mi nem, az implementaciofuggo, ugyanis a nem 'A'(ANSI) es nem 'W'(widechar) vegzodesu API hivasok (Tehat pl. a sima MessageBox) az ANSI hivasnak minosul. Ez esetben viszont az unicode (utf16) karakterek nem lesznek a helyukon kezelve, mert asszem karakterkonverzio kovetkezik be, es ugy hivodik meg a unicode fuggveny.

Magyarul az unicode support attol fugg, volt-e valaki olyan lusta, hogy ne tegye ki a W -t a fuggveny nevenek vegere.
--

()=() Ki oda vagyik,
('Y') hol szall a galamb
C . C elszalasztja a
()_() kincset itt alant.

Ha már a példánál vagyunk, nincs olyan, hogy MessageBox fv, csak MessageBoxW, és MessageBoxA van, illetve egy ilyen:

#ifdef UNICODE
#define MessageBox MessageBoxW
#else
#define MessageBox MessageBoxA
#endif

"...handing C++ to the average programmer seems roughly comparable to handing a loaded .45 to a chimpanzee."
-- Ted Ts'o

Hmmm... akkor ezek szerint #define UNICODE fuggo. Ugy emlekeztem, hogy feltetel nelkul definialja az 'A' vegu fuggvenyt postfixum nelkulive, ezek szerint tevedtem. Elnezest.
--

()=() Ki oda vagyik,
('Y') hol szall a galamb
C . C elszalasztja a
()_() kincset itt alant.

Nagyot nem tévedtél, mert ha nincs UNICODE definiálva, akkor azok hívódnak meg.

Konverzió viszont nincs, jó esetben egy warning, vagy egy error, hogy LPCWSTR-t (?, talán short*) akarsz adni LPCSTR (char*) helyett.

"...handing C++ to the average programmer seems roughly comparable to handing a loaded .45 to a chimpanzee."
-- Ted Ts'o

Milyen karakterkódolású a forrás, és milyen kódolásra van állítva a terminál? Mit mond a locale?

qDebug() << QString::fromLocal8Bit("éáíúóüű") << endl; mit mond?

--
A gyors gondolat többet ér, mint a gyors mozdulat.

A szambol vetted ki. from/toLocal8Bit() a megoldas. Mindig.
--

()=() Ki oda vagyik,
('Y') hol szall a galamb
C . C elszalasztja a
()_() kincset itt alant.

A Krusader-ben ezt használom:

QString str = QString::fromLocal8Bit( "/home/enyém/fájl.txt" );
char * fileName = str.toLocal8Bit().constData();
char linkDest[ 256 ];
readlink(fileName, linkDest, sizeof(linkDest));

Nem szokott vele baj lenni. Persze a printf lehet, hogy marhaságot nyomtat ki, de a fájlok rendben megnyitódnak. Ha a printf marhaságot ír ki, az lehet, hogy a terminál hibája.

Érdemes nem csak a konsole-t nézni, hanem más terminálokat is.