Umwandlung von UTF16/32 nach Unicode - Problem auf Linux
-
Moin
Erstes Posting, mal schaun wieviele Fehler ich mache...
Ich habe die Aufgabe gestellt bekommen, die in Java vorhandenen Properties-Klasse in c++ zu implementieren.
Als Input habe ich die gleichen Dateien, die von der Java-Klasse auch erstellt werden:
Measurement=\u6e2c\u5b9a
Wenn ich diese Zeile mit unten genannter Methode einlese und abspeichere, sieht sie folgendermaßen aus:
Measurement 測定
(hoffe man erkennt, dass das Asiatisch ist
)
Ziel der ganzen Sache ist, das ganze in Unicode-Escape-Sequenzen abzuspeichern:
Measurement µ
¼Õ«Ü
Zuerst gehe ich die Properties-Dateien zeile für Zeile durch und lese ein... wenn ein "\u" gefunden wird, stehen die 4 darauffolgenden Hex-Zeichen für ein 16bit-Wide-Character. Dann mache ich daraus wstrings und stopfe sie in diese Methode:
#include "Windows.h" std::string to_utf8(const wchar_t* buffer, int len) { int nChars = ::WideCharToMultiByte( CP_UTF8, 0, buffer, len, NULL, 0, NULL, NULL); if (nChars == 0) return ""; std::string newbuffer; newbuffer.resize(nChars) ; ::WideCharToMultiByte( CP_UTF8, 0, buffer, len, const_cast< char* >(newbuffer.c_str()), nChars, NULL, NULL); return newbuffer; } std::string to_utf8(const std::wstring& str) { return to_utf8(str.c_str(), (int)str.size()); }
Wenn alles glatt geht, erhalte ich als return-Werte normale std::strings, die dann bis zu 3 mal länger sind als der Eingabestring, aber dafür alle asiatischen (oder was auch immer) Zeichen als gültige Unicode-Escape-Sequenzen enthalten.
Langer Rede kurzer Sinn: Ich soll die ganze Prozedur auch für Linux machen, und dort habe ich weder windows.h, noch WideCharToMultiByte. Zusätzlich soll ich das mit iconv realisieren, was mir bisher mehr als eine Woche frustriertes Fehlersuchen eingebracht hat... dabei habe ich mich grob an diesem Beispiel orientiert (die aktuellen Sourcen hab ich leider nicht hier):
// welcher "Umrechnungsfaktor" muss heir gewählt werden? iconv_t cd = iconv_open ("UTF-8", "UTF-32"); iconv (cd, &src, &srclen, &dst, &dstlen); // konvertierte Strins in Datei schreiben
Das Problem: Es scheint keine einheitliche Dokumentation für iconv zu geben, und zu den widersprüchlichen Anwendungsbeispielen gesellen sich immer mehr Fragen...
- Muss man iconv einmal oder in einer while-Schleife aufrufen?
- Der Aufrufe oben ist soweit ich weiß der einzige, der den src-charpointer nicht "kaputtmacht"... wieso?
- Sind die Charsets überhaupt richtig gewählt?
Natürlich geht es auch anders, aber auch da sind meine Möglichkeiten eingeschränkt: QT steht nicht zur Verfügung, deshalb muss es irgendwie anders gehen. Iconv wurde mir quasi nahegelegt, aber wenn es darin nicht geht, bin ich natürlich für (hoffentlich funktionierende) Alternativen offen. Aber bis auf iconv habe ich irgendwie nichts gefunden...
An dieser Stelle schon mal danke fürs lesen, überfliegen oder überspringen :p und ich hoffe, dass irgendwer dazu Rat weiß.
Grüße,
Eggy
-
Dieser Thread wurde von Moderator/in SeppJ aus dem Forum C++ (auch C++0x) in das Forum Linux/Unix verschoben.
Im Zweifelsfall bitte auch folgende Hinweise beachten:
C/C++ Forum :: FAQ - Sonstiges :: Wohin mit meiner Frage?Dieses Posting wurde automatisch erzeugt.
-
iconv muss erneut mit dann vergrößerten Buffern aufgerufen werden, wenn (size_t) -1 zurückgegeben und errno auf E2BIG gesetzt wird (d.h. nicht alles in den Buffer passte). Wenn du vorher eine Obergrenze für die Länge des Ausgabestrings kennst, kann man sich das sparen, ansonsten hast du das Problem aber natürlich auch unter Windows mit MultiByteToWideChar etc.
Wie dem auch sei, ich hab da was für dich in meiner großen Kiste:
#include <iconv.h> #include <cerrno> #include <cstddef> #include <iostream> #include <string> #include <stdexcept> #include <vector> struct iconv_error : public std::logic_error { iconv_error(std::string const &message) : std::logic_error(message) { } }; struct iconv_open_error : public iconv_error { iconv_open_error(std::string const &tocode, std::string const &fromcode) : iconv_error("iconv_open fehlgeschlagen: Umwandlung von " + fromcode + " nach " + tocode + "nicht möglich") { } }; class iconverter { public: iconverter(std::string const &tocode, std::string const &fromcode, double expansion_hint = 1.2) // Abschätzungsfaktor, wie viel größer oder kleiner die Ausgabe in der Regel ungefähr sein wird. : converter_ (iconv_open(tocode .c_str(), fromcode.c_str())), expansion_hint_(expansion_hint) { if(converter_ == iconv_t(-1)) { throw iconv_open_error(tocode, fromcode); } } std::string operator()(std::string const &text) { std::size_t n_in = text.size() + 1; std::size_t n_out = static_cast<std::size_t>(text.size() * expansion_hint_) + 1; // Grobe Abschätzung der Ausgabelänge std::vector<char> outbuffer(n_out); std::vector<char> inbuffer; char *p_in, *p_out; size_t n_converted; inbuffer.reserve(n_in); inbuffer.assign(text.begin(), text.end()); inbuffer.push_back('\0'); p_in = & inbuffer[0]; p_out = &outbuffer[0]; for(;;) { errno = 0; n_converted = iconv(converter_, &p_in , &n_in, &p_out, &n_out); if(n_converted != size_t(-1)) { // Im Erfolgsfall Schleife beenden break; } else if(errno == E2BIG) { // Wenn zuwenig Platz da war: Platz verdoppeln size_t n_out_old = p_out - &outbuffer[0]; n_out += outbuffer.size(); outbuffer.resize(outbuffer.size() * 2); p_out = &outbuffer[n_out_old]; // Sonst mit Fehlermeldung raus hier. } else if(errno == EILSEQ) { throw iconv_error("Ungültige Multibyte-Sequenz in Eingabe"); } else if(errno == EINVAL) { throw iconv_error("Unvollständige Multibyte-Sequenz in Eingabe"); } else { throw iconv_error("Unbekannter Umwandlungsfehler"); } } return std::string(outbuffer.begin(), outbuffer.begin() + outbuffer.size() - n_out - 1); } ~iconverter() { iconv_close(converter_); } private: iconv_t converter_; double expansion_hint_; }; int main() { iconverter utf8_convert("UTF-8", "ISO-8859-15"); std::cout << '"' << utf8_convert("foo bar") << '"' << std::endl; std::cout << '"' << utf8_convert("ÄÖÜäöü߀") << '"' << std::endl; }
-
Das Problem: Es scheint keine einheitliche Dokumentation für iconv zu geben, und
zu den widersprüchlichen Anwendungsbeispielen gesellen sich immer mehr Fragen...* Muss man iconv einmal oder in einer while-Schleife aufrufen?
* Der Aufrufe oben ist soweit ich weiß der einzige, der den src-charpointer
* nicht "kaputtmacht"... wieso?* Sind die Charsets überhaupt richtig gewählt?
Ist die manpage so schlecht? Also ich kannte die Funktion vorher nicht und
anhand von der manpage würde ich sagenAufruf:
size_t iconv(iconv_t cd,
char **inbuf, size_t *inbytesleft,
char **outbuf, size_t *outbytesleft);* Zu 1: "The iconv() function converts one multibyte character at a time,..."
* Zu 2: Ich weiß nicht genau was du damit meinst, aber zum src-charpointer wird
* folgendes gesagt:
"
for each character conversion it increments *inbuf and decrements
*inbytesleft by the number of converted input bytes, it increments *outbuf
and decrements *outbytesleft by the number of converted output bytes, and
it updates the conversion state contained in cd.
"* Zu 3: Ich bin mir nicht sicher aber ich würde sagen ja (unter der Annahme dass
* du vorher 32-bit-wide-characters gemeint hast)Solltest du gar nicht in den manpages geguckt haben dann möchte ich dich hiermit
auf diese aufmerksam machen. Sie sind zwar sehr knapp gehalten und helfen nicht
viel wenn man gar keine Ahnung vom jeweiligen Thema hat, aber als
Nachschlagewerk sind sie (zumindest für mich) unverzichtbar
-
@linux
Linux hab ich nur am Arbeitsplatz zur Verfügung und hab auch sonst nur sehr selten damit Kontakt gehabt. Die manpage hab ich gelesen/überflogen, und jetzt im Nachhinein kann ich sagen, dass ich zum inkrementieren nen Denkfehler hatte. Aber wenn man im Netz sucht und drei Quellen sagen "rufe iconv 1 mal auf" und andere sagen "rufe iconv in einer while-Schleife auf", und wenn man selbst iconv in einer while-schleife (while inbytesleft > 0) aufruft, sich in jedem Aufruf inbytesleft ausgibt und eine einwandfreie Endlosschleife vor sich hat... da macht man sich schon Gedanken, was jetzt die richtige Methode ist.
Zu 3) Wenn das so ist, ist es ja eigentlich eher falsch, wenn der input-pointer am Ende genauso aussieht wie am Anfang, oder?@Seldon: Thx für die Klasse, wenn ich übermorgen wieder am Arbeitsplatz bin, versuche ich mal, sie zu benutzen. Darf ich doch, oder?
PS: Nein, in WideCharToMultiByte hatte ich keinerlei Probleme mit der Längenbestimmung, die hat alles automatisch gemacht.
-
Eggy schrieb:
@Seldon: Thx für die Klasse, wenn ich übermorgen wieder am Arbeitsplatz bin, versuche ich mal, sie zu benutzen. Darf ich doch, oder?
Klar, greif zu. Das Ding hat jetzt keine solche Erfindungshöhe, dass man sich über Urheberrechte prügeln müsste; ich erteile hiermit also jedem, der es brauchen kann, die Erlaubnis, es zu verwenden, verändern, weiterzugeben oder was ihm sonst noch dazu einfällt. Ich fände es allerdings nett, wenn niemand (C) jemand-der-nicht-ich-bin drüberschriebe.
Wenn Performance eine Rolle spielt, kann man da übrigens noch einiges drehen - ich hab das nur mal irgendwann nebenbei zusammengeschustert.
Eggy schrieb:
PS: Nein, in WideCharToMultiByte hatte ich keinerlei Probleme mit der Längenbestimmung, die hat alles automatisch gemacht.
Ah, das erste mal prüft, wie viel Platz gebraucht wird, und beim zweiten mal kannst du gleich genug bereitstellen. Ich glaube, iconv stellt eine solche Schnittstelle nicht zur Verfügung; jedenfalls ist mir keine bekannt.
Und wo ich das gerade so lese:
::WideCharToMultiByte( CP_UTF8, 0, buffer, len, const_cast< char* >(newbuffer.c_str()), nChars, NULL, NULL);
ist nicht standardkonform. Niemand garantiert dir, dass newbuffer.c_str() == newbuffer.data(), oder auch nur, dass std::string seine Daten an einem Stück speichert. Das ist auch der Grund, warum ich den Umweg über std::vector gehe - da gibt es entsprechende Garantien.
-
Sooo, jetzt gehts mit den Problemen los:
Ich hab als Input ja einen std::wstring, oder einen wchar_t, wie mans nimmt, aber iconv schluckt ja nur normale char-pointer. Bei der Umwandlung von wchar_t* nach char* konnte ich beobachten dass aus "abc" jeweils "a b c " wird (leerzeichen steht für dreimal '\0'). Wie krieg ich das also so umgewandelt, dass iconv es schluckt und wie gehe ich mit der vierfachen Länge um?
Außerdem ist es hiermit
iconverter utf8_convert("UTF-8", "ISO-8859-15");
sicher nicht getan, ich gebe ja am Anfang kein ISO rein, sondern UTF16 oder 32. Eventuell besteht sogar die Möglichkeit, den vervierfachten Nullbytestring von oben hiermit abzufangen? Hier weiß ich nicht wirklich weiter...
Grüße,
Eggy