Charset Frage



  • Servus Com,

    ich habe eine Frage zur Codierung von Unicode. Ich habe einen String in dem die Umlaute via json im Format [u]00fc (ü) übergeben werden. Ich versuche diese umzuwandeln in ein wsstring \u00fc jedoch gelingt mir das nicht durch das escapen.

    Muss ich beim replace irgendetwas beachten an Sonderzeichen?

    Hier mal ein Codebeispiel:

    std::string json_string = "Json mit dem [u]00fc";
    
    std::wstring json_wstring(json_string.begin(), json_string.end());
    
    
    int uni_pos = json_string.find("[u]");
    
    std::string uni_hash = json_string.substr(uni_pos + 3, 4);
    
    std::wstring w_uni_string = L"\\u" + uni_hash;
    
    boost::replace_all(json_wstring, "[u]" + uni_hash, w_uni_string);
    

    Dabei gibt w_uni_string dann nicht das ü sondern \u00fc aus.

    Lg Skared



  • @Skared sagte in Charset Frage:

    ich habe eine Frage zur Codierung von Unicode. Ich habe einen String in dem die Umlaute via json im Format [u]00fc (ü) übergeben werden. Ich versuche diese umzuwandeln in ein wchar_t \u00fc jedoch gelingt mir das nicht durch das escapen.

    du muss 00fc nach binär umwandeln, also nach 252
    die 00 sind das hi-byte (also 0 * 256) und fc ist das low byte (252)
    in ein wchar_t passt das dann noch, aber ein unicode-zeichen kann auch 3 oder 4 bytes lang sein. dann geht das nicht mehr.

    besser wäre wohl du benutzt eine json-library. die haben alle eine stringify-funktion, die dir viel arbeit abnimmt.
    auf der seite unten findest du welche: https://www.json.org/json-en.html



  • @Bushmaster sagte in Charset Frage:

    in ein wchar_t passt das dann noch, aber ein unicode-zeichen kann auch 3 oder 4 bytes lang sein. dann geht das nicht mehr.

    Aber auch nur wenn es utf-8 kodiert ist. Da kann ein zeichen aus bis zu 4 bytes bestehen. Und UTF-8 ist auch die einzige byte kodierung für Unicode.
    Bei UTF-16 ist ein codepoint 16bit breit und ein zeichen kann aus bis zu 2 codepoints bestehen.
    Bei UTF-32 ist ein codepoint 32bit breit.
    wchar_t unter windows ist 16 bit groß und wird für UTF-16 kodiertes Unicode verwendet werden.
    Unter linux ist wchar_t aber 32bit breit und kann für UTF-32 kodiertes Unicode verwendet werden.

    Siehe auch: https://naveenr.net/unicode-character-set-and-utf-8-utf-16-utf-32-encoding/

    @ Skared: Dein [u]00FC steht für das ü im Unicode Zeichensatz.
    In den verschiedenen Kodierungen wird das wie folgt kodiert:

    • UTF8: c3 bc
    • UTF-16: 00fc
    • UTF-32: 000000fc

    @Bushmaster sagte in Charset Frage:

    die 00 sind das hi-byte (also 0 * 256) und fc ist das low byte (252)
    Aber auch nur für UTF-16BE (Big Endian), was UTF-16 unter windows entspricht. es gibt aber auch noch UTF_16LE (Little Endian) da wäre es dann: fc00

    Um das endiness problem zu vermeiden sollte man texte generell in utf-8 kodiert speichern.



  • Vielen Dank für eure Antworten.

    Mir geht es nur darum wie ich aus dem [u]hash ein \uhash mache welches in einem wstring dann ersetzt und dann auch richtig dargestellt wird. Aktuell wertet er jeden Buchstaben/Zahl und Backslash als 6 verschiedene Zeichen obwohl sie ein Zeichen sein sollten.

    Lg Skared



  • Die Notation \uxxxx wird AFAIK vom kompiler ausgewertet.
    Im fertigen string steht das zeichen in der entsprechenden Unicode kodierung.

    z.b. Hülle: H\u00fclle -> im wchar_t based string (windows, 16bit) sind es dann folgende werte (in hexadezimaler schreibweise)
    0x0048 0x00fc 0x006c 0x006c 0x0065.

    Das heißt du musst dich selbst um die Umwandlung in die korrekte Unicode kodierung kümmern. Oder eine Library verwenden, welches das unterstützt (ich kenne da aktuell keine)

    Aber was spricht dagegen den JSON text direkt in einer der UTF-X kodierungen zu erzeugen (am besten in UTF-8) und dann einzulesen?
    Dann wäre dieses Problem weg.

    Und falls du mindestens c++11 verwenden kannst, dann gibt es in der STL support für die konvertierung zwischen den UTF-X Kodierungen. z.b. für UTF-8 nach UTF-16

    #include <iostream>
    #include <string>
    #include <locale>
    #include <codecvt>
     
    int main()
    {
        std::string utf8 =  u8"z\u00df\u6c34\U0001d10b"; // or u8"zß水𝄋"
                            // or "\x7a\xc3\x9f\xe6\xb0\xb4\xf0\x9d\x84\x8b";
     
        // the UTF-8 / UTF-16 standard conversion facet
        std::u16string utf16 = std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t>{}.from_bytes(utf8.data());
        std::cout << "UTF16 conversion produced " << utf16.size() << " code units:\n";
        for (char16_t c : utf16)
            std::cout << std::hex << std::showbase << c << '\n';
    }
    

    Quelle: https://en.cppreference.com/w/cpp/locale/wstring_convert/from_bytes



  • Wie kommst du darauf, dass [u]00fc ein ü darstellt?



  • @manni66 sagte in Charset Frage:

    Wie kommst du darauf, dass [u]00fc ein ü darstellt?

    Vermutlich soll [u]00fc das gleiche sein wie U+00FC (die offizielle Notation in Unicode Character tables). Und dort steht das für ein 'ü'

    z.b. hier: https://www.utf8-chartable.de/



  • Servus,

    vielen Dank für eure Antworten jedoch hilft mir davon keine weiter. Wenn ihr euch meinen Quellcode anschaut seht ihr ja was mein Problem ist und da geht es nicht darum wie ich darauf komme das [u]00fc ein ü ist.

    Mein Problem ist das ich [u]00fc nicht in \u00fc umgewandelt bekomme ohne das Zeichen zu zerstören da der Backslash escaped werden muss nehme ich an.

    Liebe Grüße



  • @Skared sagte in Charset Frage:

    Mein Problem ist das ich [u]00fc nicht in \u00fc umgewandelt bekomme ohne das Zeichen zu zerstören

    so etwa:

    #include <string>
    #include <regex>
    
    using namespace std;
    
    int main()
    {
    	string alt = "das ist ein string mit [u]00fc darin";
    	string neu = regex_replace(alt, regex("\[u\]"), "\\u");
    	cout << alt << "\n" << "wird zu:\n" << neu << endl;
    }
    
    

    da wird nichts zerstört.



  • @Skared

    Da [u]00fc kein Zeichen ist, kannst du es auch nicht zerstören.

    Auch wenn du die drei Zeichen [u] durch die zwei Zeichen \u ersetzt, wird daraus kein ü. Auch nicht in einem wstring.



  • @manni66 sagte in Charset Frage:

    @Skared

    Da [u]00fc kein Zeichen ist, kannst du es auch nicht zerstören.

    Auch wenn du die drei Zeichen [u] durch die zwei Zeichen \u ersetzt, wird daraus kein ü. Auch nicht in einem wstring.

    er will ja scheinbar ein unicode-literal haben. vielleicht will er einen quelltext generieren?



  • @Skared sagte in Charset Frage:

    Mein Problem ist das ich [u]00fc nicht in \u00fc umgewandelt bekomme ohne das Zeichen zu zerstören da der Backslash escaped werden muss nehme ich an.

    Was willst du denn letztendlich nach dem Umwandeln in deinem Unicode-codierten String tatsächlich stehen haben? Ein ü oder buchstabengetreu \u00fc?

    Irgendwie erschliesst sich mir das nicht so richtig. Ich tippe mal auf das ü. In dem Fall ist die Lösung natürlich etwas komplizierter, da man den Code Point parsen und in eine gültige UTF-8/16-Sequenz von Code Units umwandeln muss muss.

    Ich habe damit mal etwas herumgespielt und meine Lösung sieht so aus (nur minimal getestet und wohl nicht super-effizient):

    #ifdef _MSC_VER
        #define _SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING
    #endif
     
    #include <cstddef>
    #include <cstdlib>
    #include <iostream>
    #include <string>
    #include <algorithm>
    #include <regex>
    #include <codecvt>
     
    #ifdef _WIN32
        #if !defined(NOMINMAX)
            #define NOMINMAX
        #endif
        #if !defined(WIN32_LEAN_AND_MEAN)
            #define WIN32_LEAN_AND_MEAN
        #endif
        #include <Windows.h>
    #endif
     
    auto main() -> int
    {
        #ifdef _WIN32
            SetConsoleOutputCP(CP_UTF8);
        #endif
     
        std::string json_string  = "Json mit dem UE [u]00fc, dem AE [u]00e4 und der Kuh [u]1f42e ;-)";
     
        std::regex unicode_regex("\\[u\\]([0-9abcdefABCDEF]{2,8})");
        std::wstring_convert<std::codecvt_utf8<char32_t>, char32_t> convert;
     
        std::string utf8_string;
        std::size_t i = 0;
     
        std::for_each(
            std::sregex_iterator{
                std::cbegin(json_string),
                std::cend(json_string),
                unicode_regex
            },
            std::sregex_iterator{},
            [&](std::smatch match){
                // JSON-String von letzter Position bis zum Match anhängen.
                utf8_string.append(json_string.substr(i, match.position() - i));
                // Submatch via strtoul in char32_t umwandeln, nach UTF-8
                // konvertieren und anhängen.
                utf8_string.append(
                    convert.to_bytes(
                        std::strtoul(match.str(1).c_str(), nullptr, 16)
                    )
                );
                i = match.position() + match.length();
            }
        );
        // Rest des JSON-String anhängen.
        utf8_string.append(json_string.substr(i));
     
        std::cout << json_string << std::endl;
        std::cout << utf8_string << std::endl;
    }
    

    Ausgabe (https://ideone.com/9REf7I):

    Json mit dem UE [u]00fc, dem AE [u]00e4 und der Kuh [u]1f42e ;-)
    Json mit dem UE ü, dem AE ä und der Kuh 🐮 ;-)
    

    Bezüglich Kuh: Nicht-fixe Anzahl von Hex-Ziffern ist bei der Syntax wahrscheinlich keine gute Idee, wenn auch mal Wörter wie "Äffchen" drin vorkommen sollen. Vielleicht besser das Regex zu "\\[u\\]([0-9abcdefABCDEF]{4})" abändern 😉

    Ich verwende das von @firefly angeregte std::wstring_convert um vom via Regex geparsten Code Point (via std::strtoul in einen Integer umgewandelt) nach UTF-8 zu konvertieren.

    Man muss allerdings erwähnen, dass std::wstring_convert ab C++17 deprecated ist und ich wüsste jetzt auch keine Alternative außer Drittbibliotheken.

    Wenn du UTF-16 brauchst, dann hat @firefly ja schon gezeigt, wie man das weiter konvertieren kann. Ich an deiner Stelle würde aber seinen Rat befolgen und wenn möglich das JSON gleich in der korrekten Kodierung erzeugen - ohne diese [u]-Krücke. Natürlich nur, falls das möglich ist und du Einfluss auf die Erstellung der JSON-Dateien hast.



  • Windows:

    #include <cctype>
    #include <cstring>
    #include <string>
    #include <sstream>
    #include <iostream>
    
    #include <io.h>
    #include <fcntl.h>
    
    std::wstring strange_encoding_to_utf_16(std::string const& str)
    {
    	auto const indicator{ "[u]" };
    	auto const indicator_length{ std::strlen(indicator) };
    	std::wstring result;
    
    	for (std::size_t pos{ str.find(indicator) }, old_pos{};
    	     pos != std::string::npos && pos + indicator_length + 4 <= str.length();
    	     old_pos = pos,
    	         pos = str.find(indicator, pos))
    	{
    		result += std::wstring{ str.data() + old_pos, str.data() + pos };
    		auto num_pos{ pos + indicator_length };
    
    		if (std::isxdigit(str[num_pos + 0]) && std::isxdigit(str[num_pos + 1]) &&
    			std::isxdigit(str[num_pos + 2]) && std::isxdigit(str[num_pos + 3]))
    		{
    			std::istringstream iss{ std::string{ str.data() + num_pos, str.data() + num_pos + 4 } };
    			unsigned long value;
    			iss >> std::hex >> value;
    			result += static_cast<wchar_t>(value);
    			pos = num_pos + 4;
    		}
    		else {
    			result += std::wstring{ indicator, indicator + indicator_length };
    			pos += indicator_length;
    		}
    	}
    
    	return result;
    }
    
    int main()
    {
    	static_assert(sizeof(wchar_t) != 4, "sizeof wchar_t must be 4.");
    
    	_setmode(_fileno(stdout), _O_U16TEXT);
    
    	std::string json_string = "Json mit dem [u]00fcfoo bar baz [u]0041[u]0042[u]0043.";
    
    	std::wcout << std::wstring{ json_string.cbegin(), json_string.cend() } << L'\n'
    	           << strange_encoding_to_utf_16(json_string) << L"\n\n";
    }
    


  • Guten morgen,

    vielen Dank an alle. Ich habe mein Problem mit eurer Hilfe lösen können 🙂

    Liebe Grüße



  • @Skared sagte in Charset Frage:

    vielen Dank an alle. Ich habe mein Problem mit eurer Hilfe lösen können 🙂

    Also doch ein ü und kein buchstabengetreues \u00fc?

    Wichtige Erkenntnis: Diese escapten Zeichen mit Backslash werden tatsächlich nur beim Kompilieren ausgewertet um daraus die Zeichen in der jeweiligen Codierung zu generieren. cout oder Strings können damit nichts anfangen und geben dir diese Escape-Sequenzen exakt so aus, wie sie im String stehen.



  • @Swordfish sagte in Charset Frage:

    Windows: ...

    Das mutet schon etwas barock an 😉

    Interessant ist aber der der Ansatz, dass man die Umwandlung natürlich (theoretisch) etwas simpler hinbekommt, indem man die aus mehreren Code Units bestehenden UTF-16 Code Points einfach aussen vor lässt. Damit deckt man natürich nicht den gesamten Unicode-Bereich ab, aber je nach Anwendungsgebiet kann das schon ausreichen und den Code etwas vereinfachen. Vielleicht noch den zulässigen Wertebereich einschränken, damit die Funktion kein ungültiges UTF-16 erzeugen kann? Das würde ich jedenfalls nicht in die Hand desjenigen legen wollen, der die JSON-Datei schreibt 😉

    Hätte ich das manuell gemacht, anstatt es an std::wstring_convert zu delegieren, wäre das Ganze deutlich länger geworden.

    Auch das mit dem _setmode(_fileno(stdout), _O_U16TEXT) kannte ich noch nicht. Könnte das vielleicht eine halbwegs portable Lösung sein, um die Konsole auf UTF-16 umzustellen? Linux kennt diese Funktion meines Wissens ebenfalls (wenn auch glaube ich ohne die Unterstriche).



  • @Skared sagte in Charset Frage:

    vielen Dank an alle. Ich habe mein Problem mit eurer Hilfe lösen können 🙂

    Noch ein wichtiger Hinweis: Die Konvertierung in meinem Code kann einen std::range_error werfen (siehe hier). Das kann je nachdem wo die JSON-Daten herkommen, durchaus öfter mal passieren und sollte dein Programm nicht unbedingt "crashen" lassen.

    Entweder du fängst das entsprechend ab, oder konstruierst das std::wstring_convert-Objekt in Zeile 32 so:

    std::wstring_convert<std::codecvt_utf8<char32_t>, char32_t> convert{ "<invalid character>" };
    

    Der im Konstruktor übergebene String wird dann in Fehlerfall in den konvertierten String eingefügt anstatt dass eine Exception geworfen wird.



  • @Finnegan sagte in Charset Frage:

    Das mutet schon etwas barock an

    Was gefällt Dir daran nicht? Na gut, einen stringstream auf eine immer 4-stellige Zahl zu werfen ist overkill.



  • @Swordfish sagte in Charset Frage:

    @Finnegan sagte in Charset Frage:

    Das mutet schon etwas barock an

    Was gefällt Dir daran nicht? Na gut, einen stringstream auf eine immer 4-stellige Zahl zu werfen ist overkill.

    Das, und man hätte vielleicht auch noch irgendwie den restlichen Schleifenkörper in den Kopf bekommen können 😉

    Ein indicator vom Typ std::string würde den Code auch etwas übersichtlicher machen, wenn die Variable eh nicht constexpr ist (brauchts auch nicht wegen Effizienz, Konstantenfaltung wird das schon regeln).

    Die String-Kopiererei mit den ganzen mallocs unter der Haube hab ich allerdings auch drin. Für den Feinschliff würd ich das wohl mit string_views machen. So ein Algorithmus braucht eigentlich nur auf dem Eingabe- und dem Ausgabestring zu arbeiten.



  • @Finnegan sagte in Charset Frage:

    wenn die Variable eh nicht constexpr ist

    Ein constexpr Stringliteral? Wie geht das?


Anmelden zum Antworten