UTF-8



  • Hallo ^^ Ich versuche zur Zeit krampfhaft einen UTF-8 encodeden String in einen wide-string zu convertieren... Zu erst habe ich mit std::bitset rumhantiert, aber dann habe ich gemerkt, dass wchar_t von allein weiß, wie viele char`s zu ihm gehören, also kam ich auf folgende relativ logische Lösung:

    std::wstring test(const char *iter)
    {
    	std::wstring R(L"");
    	std::wstring::value_type toadd;
    	do
    	{
    		toadd = *iter;
    		++iter;
    		if (toadd>127)
    			++iter;
    		if (sizeof(toadd)==4) //in GCC und Co sollte das 4 Byte sein... Unter Windows 2... Das hätte natürlich den Nachteil, dass es nicht kompatibel zueinander wäre - aber das ist im Moment mein kleinstes Problem
    		{
    			if (toadd>65535)
    				++iter;
    			if (toadd>16777215)
    				++iter;
    		}
    		R.push_back(toadd);
    	} while (toadd != L'\0');
    	return R;
    }
    

    Dummerweise geht das aber nicht ><

    Als Beispiel habe ich mal folgendes genommen:

    http://de.wikipedia.org/wiki/UTF-8
    (Beispiele für UTF-8 Kodierungen und dort das zweite - ein ä)

    die beiden char`s, die das ä eigtl darstellen, sind noch richtig dargestellt, aber danach stimmte irgendwie gar nichts mehr ><

    Das kam mir alles schon sehr komisch vor... Also hab ich mal folgendes getestet:

    union utf_8
    {
    wchar_t right;
    char wrong[sizeof(wchar_t)]; //2, da MSVC als Compiler
    };
    
    char t1 = 0xC3; //-61
    char t2 = 0xA4; //-92
    utf_8 T1, T2, T3;
    T1.wrong[0] = t1;
    T1.wrong[1] = t2;
     //right == 42179
    
    T2.right = 0xA4C3;
     //right == 42179
    
    T3.right = L'ä'; //-28
     //right == 228
    

    Dann habe ich auch noch versucht, das zeichen mit shiften uns do hinzubekommen - da ich nicht genau wusste, wie rum die beiden werte kommen müssen, habe ich beides versucht:

    wchar_t temp;
    
    char t1 = 0xC3;
    char t2 = 0xA4;
    
    temp = t1;
    temp = temp<<8;
    wchar_t Z1 = temp + t2;
    temp = t2;
    temp = temp<<8;
    wchar_t Z2 = temp + t1;
    

    Auch das ging nicht...

    Als allerletztes dann noch:

    char t1 = 0xC3;
    char t2 = 0xA4;
    
    char *asd = new char[2];
    asd[0] = t1;
    asd[1] = t2;
    
    wchar_t temp = *asd;
    

    auch hier habe ich t1 und t2 noch mal vertauscht - aber das war auch nicht des rätsels lösung ><

    wo liegt mein denkfehler?

    danke schon mal : >
    bb, Tom





  • Hm, gemäß dem Wikipedia-Artikel würde ich in etwa so rangehen:

    if ( *iter <= 127 )
        // 1 Byte lang
    else if ( (*iter&0xE0) == 0xC0 )
        // 2 Bytes lang
    else if ( (*iter&0xF0) == 0xE0 )
        // 3 Bytes lang
    else if ( (*iter&0xF8) == 0xF0 )
        // 4 Bytes lang
    else
        // Fehler
    

    Dann halt die Fälle einzeln behandeln.



  • @Badestrand:
    Danke, aber anscheind geht es ja auch mit boost ganz gut(?)!?

    @Unicoder:
    Danke - ich habs jetzt versucht mit boost zu machen - allerdings habe ich den string nicht in einer datei - daher habe ich nun ein relativ großes problem - mein bisherirger relativ schlechter erster versuch:

    [edit:]

    #include <boost/archive/detail/utf8_codecvt_facet.hpp>
    
    #include <locale>
    #include <sstream>
    #include <string>
    
    std::wstring Convert_from_UTF_8(const char *str)
    {
    	std::locale old_locale;
    	std::locale utf8_locale(old_locale, new boost::archive::detail::utf8_codecvt_facet);
    
    	// Set a New global locale
    	std::locale::global(utf8_locale);
    
    	// Read the UTF-8 data back in, converting to wchar_t on the way in
    	const size_t strLength(strlen(str));
    	std::stringstream ss;
    	ss.write(str, strLength);
    
    	std::wstring R(L"");
    	R.reserve(strLength);
    
    	ss.imbue(utf8_locale);
    
    	wchar_t item('\0');
    	while (ss >> item)
    	{
    		R.push_back(item);
    	};
    	return R;
    }
    

    Allerdings kann das ja vorn und hinten nicht klappen - ist ja auch ganz logisch (stringstream + wchar_t + ...)
    Ich hoffe, dass mir da jemand helfen kann 😞

    Danke schon mal 🙂

    bb

    2. edit:
    ist das include überhaupt richtig?
    und welche lib müsste ich dafür bauen?

    das stinkt doch alles 😞

    naja - vll weiß ja jmd von euch weiter?!
    danke 🤡

    bb



  • unskilled schrieb:

    @Badestrand:
    Danke, aber anscheind geht es ja auch mit boost ganz gut(?)!?

    Wahrscheinlich schon, ja. Weil Einschlafen nicht immer so gut funktioniert, hab ich kurz mal ein wenig gebastelt, falls es dich interessiert, wie es händisch funktionieren könnte 🙂
    (Fehlerbehandlung ist quick'n dirty)

    #include <vector>
    #include <iostream>
    
    int cvtUtf8ToUtf32Char( const unsigned char*& iter, const unsigned char* end )
    {
    	if ( *iter <= 127 )
    	{
    		// 1 Byte lang
    		if ( end-iter < 1 )
    			return ++iter, 0; // Fehler
    		return *(iter++);
    	}
    	else if ( (*iter&0xE0) == 0xC0 )
    	{
    		// 2 Bytes lang
    		if ( end-iter < 2 )
    			return ++iter, 0; // Fehler
    		int a = (*(iter++) & 0x1F) << 6;
    		int b = (*(iter++) & 0x3F);
    		return a | b;
    	}
    	else if ( (*iter&0xF0) == 0xE0 )
    	{
    		// 3 Bytes lang
    		if ( end-iter < 3 )
    			return ++iter, 0; // Fehler
    		int a = (*(iter++) & 0x0F) << 12;
    		int b = (*(iter++) & 0x3F) << 6;
    		int c = (*(iter++) & 0x3F);
    		return a | b | c;
    	}
    	else if ( (*iter&0xF8) == 0xF0 )
    	{
    		// 4 Bytes lang
    		if ( end-iter < 4 )
    			return ++iter, 0; // Fehler
    		int a = (*(iter++) & 0x07) << 18;
    		int b = (*(iter++) & 0x3F) << 12;
    		int c = (*(iter++) & 0x3F) << 6;
    		int d = (*(iter++) & 0x3F);
    		return a | b | c | d;
    	}
    	else
    	{
    		return ++iter, 0; // Fehler
    	}
    }
    
    std::vector<int> cvtUtf8ToUtf32String( const unsigned char* utf8_str, const unsigned char* end )
    {
    	std::vector<int> w;
    	while ( utf8_str < end )
    		w.push_back( cvtUtf8ToUtf32Char( utf8_str, end ) );
    	return w;
    }
    
    int main()
    {
        //                        y        ä     eingetr. Marke   Eurozeichen     Violinschlüssel
    	//                     U+0079 |  U+00E4   |  U+00AE  | U+20AC          | U+1D11E
    	unsigned char utf8[] = { 0x79, 0xC3, 0xA4, 0xC2, 0xAE, 0xE2, 0x82, 0xAC, 0xF0, 0x9D, 0x84, 0x9E };
    
    	std::vector<int> w = cvtUtf8ToUtf32String( utf8, utf8+sizeof(utf8) );
    	for ( size_t i=0; i<w.size(); i++ )
    		std::cout << std::hex << w[i] << std::endl;
    }
    

    PS: vector<int> statt wstring, weil wchar_t ja auch nur 16 Bits groß sein kann, würde der Violinschlüssel dann z.B. nicht mehr reinpassen.



  • danke - mir gings ähnlich und statt zu schlafen hab ich es so gebaut:

    std::wstring Convert_from_UTF_8(const char *str)
    {
    	std::wstring R(L""); //der Ausgabestring
    	R.reserve(strlen(str)); //zu viel reservieren kann ja nicht schaden...
    
    	unsigned char tmp;
    	size_t anz;
    	std::wstring::value_type toAdd, tmpToAdd;
    	while (*str != '\0') //bis wir das ende erreicht haben...
    	{
    		tmp = *str++;
    		if (!(tmp & 0x80)) //1 Byte
    		{
    			R.push_back(tmp); //...ranhängen
    			continue; //...und weiter
    		}
    		if (tmp < 0xE0) //das dritte bit ist _nicht_ gesetzt
    		{
    			anz = 2; //also gibt es 2 Byte
    			tmp &= 0x1F; //und wir haben die 3 ersten bits zu 0en
    		}
    		else if (tmp < 0xF0) //das vierte bit und nichts darüber ist gesetzt
    		{
    			anz = 3; //3 Byte
    			tmp &= 0xF; //wir müssen 4 bits 0 setzen
    		}
    		else if (tmp <= 0xF8) //das fünfte bit
    		{
    			anz = 4; //4 Byte
    			tmp &= 0x7; //wir setzen also 5 bits auf 0
    		}
    		else //mehr als 4 byte erlauben wir nicht...
    			throw std::invalid_argument("invalid utf-8 encoding"); //...und werfen eine exception
    
    		if ((sizeof(wchar_t) == 2) && (anz > 3)) //sizeof sollte wegoptimiert werden
    		{ //wir können in den 2 Byte nur ein max. 3 Byte langes UTF-8 encodedes Zeichen speichern
    			str += anz-1; //wir überspringen all die betroffenen Bytes...
    			R.push_back(L'?'); //...und geben einfach nen Fragezeichen aus...
    			continue; //...das sollte reichen - zum nächsten Zeichen
    		}
    
    		toAdd = tmp; //wir brauchen zum Shiften nen größeren Typ
    		toAdd <<= (anz-1)*6; //anz-1 Byte folgen noch... aber sie sind alle nur 6 bit lang, weil sie mit 10...... anfangen
    
    		while (--anz)
    		{
    			tmp = *str++;
    			tmpToAdd = tmp & 0x3F; //die ersten beiden bits werden nicht gebraucht - 'xxxx xxxx' & '0011 1111' => '00xx xxxx'
    			tmpToAdd <<= (anz-1)*6; //wir shiften einfach 6*<folgende bytes> nach links
    			toAdd |= tmpToAdd; //und addieren das zum bisherigen Zeichen
    		}
    		R.push_back(toAdd);
    	}
    	return R;
    }
    

    vll nicht gerade perfekt, aber auch nicht das allerschlechteste - oder? ^^



  • Ja, sieht auf den ersten Blick gut aus 👍



  • ich weiß - ist schon ein wenig älter, aber hätte noch ne frage:

    myself schrieb:

    if ((sizeof(wchar_t) == 2) && (anz > 3)) //sizeof sollte wegoptimiert werden
    

    anscheind wird es auch wegoptimiert, aber dummerweise bringt mir mein compiler ja trotzdem ne warnung von wegen konstanter ausdruck... gibt es irgend ne möglichkeit mit hilfe des präprozessors zu prüfen, wie viel byte wchar_t belegt?

    was haltet ihr allg. von der fkt.? würdet ihr so einsetzen oder sehr ihr vll fehler/...?

    danke


Anmelden zum Antworten