Umlaute/Sonderzeichen aus ASCII-Datei nach Unicode konvertieren?
-
Hallo,
ich habe folgendes Problem:
Mein Programm bekommt vom Benutzer eine Textdatei übergeben, diese wird dann geparst. Nun will ich
1. einzelne Wörter mit welchen aus Programmeigenen Dateien vergleichen
2. Umlaute/Sonderzeichen im Output durch deren (Unicode-)Zahlenwert ersetzen.Das Problem ist, dass ein 'ä' in einer ASCII-Codierten Textdatei anders gespeichert ist als ein 'ä' in einer UTF-XX-Datei. Die beiden sollen aber gleich behandelt werden.
Ich benutze std::wfstream und std::wstring zur Datenverarbeitung, Compiler ist MinGW 4.5.1. (soll aber portabel sein)
Wie bringe ich mein Programm dazu, sich bei diesen Sonderzeichen richtig zu verhalten (möglichst ohne externe Bibliotheken)?
1. Kann man aus der Datei auslesen, ob sie UTF-XX oder ASCII-Codiert ist und dementsprechend irgendwelche konvertierungen vornehmen)?
2. Könnte man alternativ schauen, ob Unicode-Leading-Bytes (BOM) angeführt sind und wenn das nicht der Fall ist, dann dem User eine Warnung/einen Error ausgeben, dass er seine Datei nicht ASCII-Codiert abspeichern soll?
3. Geht es gar noch besser oder nur anders?
4. Geht das mit externen Libraries besser?(jetzt mal ohne die ganzen Unicode-Spezialitäten wie Combining Characters zu berücksichtigen, siehe hier)
mfg
-
Hallo,
Lies mal das hier
http://www.c-plusplus.net/forum/261215-full
-
http://floern.com/webscripting/is-utf8-auf-utf-8-prüfen sollte sich 1-zu-1 in C umsetzen lassen.
-
Danke!
Sieht also danach aus, als müsste ich wohl oder übel auf die BOM schauen und ggf. einen Fehler bei ASCII-Codierung ausgeben, da ich ja die jeweiligen Codepages, mit denen die Datei verfasst wurde, nicht kenne (ich kann zwar in der Locale nachschauen, aber die Datei könnte ja verschickt worden sein). Ich probier mal aus, inwiefern das geht.
mfg
-
So, jetzt habe ich mich mal umgesehen.
Da der volle UTF-Zeichensatz gar nicht in einem std::wstring gespeichert werden kann, da ein wchar_t bei mir nur 2 Bytes groß ist, bräuchte ich eine Alternative.
Möglich wäre vielleicht, die std::uXXstring-Klassen zu benutzen, wobei ich dann aber wieder stream-Operatoren überladen müsste, weil es (noch) kein std::uXXcout gibt.
Bliebe also doch eine externe Library, die dann aber auch z.B. Threading und evtl. andere Features wie Dateifunktionen etc. enthalten sollte. ICU scheidet schon mal aus, Boost bietet anscheinend auch keine String-Klasse. Blieben dann nur noch wxWidgets oder Qt mit "Konsolen-Ausstattung"?EDIT: Vielen Dank übrigens, SG1, die Funktion funktioniert exzellent!
-
Klar passt der volle Zeichenumfang in einen (visual c++ 2 byte wchar_t) wstring. Per Utf16-Kodierung halt. Genauso wie der volle Zeichenumfang auch in einen string passt per Utf8-Kodierung.
-
Decimad schrieb:
Klar passt der volle Zeichenumfang in einen (visual c++ 2 byte wchar_t) wstring. Per Utf16-Kodierung halt. Genauso wie der volle Zeichenumfang auch in einen string passt per Utf8-Kodierung.
Ja klar. Aber wenn ich dann per str.at(i) oder str[i] auf den String zugreife, stimmt dann ja die Buchstabenzahl nicht mehr. Da müsste ich mir dann gleich eine Unicode-Zugriffsfunktion schreiben. Bei UTF-16 können nämlich 2 wchar_t hintereinander ein Zeichen bedeuten und bei UTF-8 bis zu 4 chars.
-
Das ist mir durchaus bewusst! Allerdings bietet Unicode auch mit 32-Bit-Kodierung einige Schwierigkeiten, die allzu intuitive Verwendung verhindern.
Nichts sollte dich übrigens daran hindern, einen std::basic_string<uint32> zu benutzen, oder etwa doch? Dachte, das wäre recht generisch.
Wenn du anschließend die UTF32-Kodierung benutzt, musst du natürlich ständig konvertieren um mit den Betriebssystem-Apis deiner Wahl zu kommunizieren... Soweit ich weiß benutzen die zumeist UTF8 oder UTF16.
-
Decimad schrieb:
Das ist mir durchaus bewusst! Allerdings bietet Unicode auch mit 32-Bit-Kodierung einige Schwierigkeiten, die allzu intuitive Verwendung verhindern.
Nichts sollte dich übrigens daran hindern, einen std::basic_string<uint32> zu benutzen, oder etwa doch? Dachte, das wäre recht generisch.
Wenn du anschließend die UTF32-Kodierung benutzt, musst du natürlich ständig konvertieren um mit den Betriebssystem-Apis deiner Wahl zu kommunizieren... Soweit ich weiß benutzen die zumeist UTF8 oder UTF16.Das meinte ich ja mit dem uXXstring, also dann entsprechend u32string. Dann müsste ich halt den ostream-Operator überladen und die string-to-number-und-zurück-Funktionen neu implementieren, was aber möglich wäre. Die einzigen Betriebssystemfunktionen, die ich benutze, sind Datei öffnen/schließen, aber dafür benutze ich sowieso std::string als Filename-Speicher.
-
Also nur um es nochmal zusammenzufassen, was du eigentlich willst.
Du möchtest Dateien in allen UTF und Ansi-Kodierungen (Die Ansi-Codepage möchtest du hierbei "erraten") laden, in UTF32 umwandeln, dann damit arbeiten und das Ergebnis dann in einer UTF32-Datei abspeichern?
-
Decimad schrieb:
Also nur um es nochmal zusammenzufassen, was du eigentlich willst.
Du möchtest Dateien in allen UTF und Ansi-Kodierungen (Die Ansi-Codepage möchtest du hierbei "erraten") laden, in UTF32 umwandeln, dann damit arbeiten und das Ergebnis dann in einer UTF32-Datei abspeichern?Fast. Die Codepages will ich bei ANSI nicht "erraten", ich lasse dann nur Zeichen von 0-127 zu, ansonsten wird ein Fehler ausgegeben. Die restlichen Codierungen werden zu UTF-32 konvertiert (oder eben in einem entsprechenden UTF-XX-String einer externen Library gespeichert, der da gescheit darauf zugreifen kann) und "bearbeitet". Der Output muss dann vor dem Herausschreiben nach UTF-8 umgewandelt werden.
-
Na denn ist das doch gar kein Problem, denn ein ä findet sich doch sowieso nicht in den unteren 127 stellen! Das dümpelt doch erst auf Stelle 228 in der ISO-8859-1 rum. Dann hast du ja außer im Fehlerfall auch direkt UTF8 und kannst das zudem durch einen pro-Buchstaben-Cast von Byte auf Dword in UTF32 umwandeln, wenn ich das richtig sehe.
-
Decimad schrieb:
Na denn ist das doch gar kein Problem
Ja, jetzt nicht mehr
.
Wenn das mit den Codepages nicht geht, muss man das halt lassen.
-
Hehehe
-
So, für alle die, die's interessiert, hier der Code:
#include <string> #include <limits> typedef std::u32string str_t; typedef char32_t char_t; void Error(const char_t *); size_t utils::binary::GetLeadingZerosCount(char_t in) { int bits = sizeof(in) * CHAR_BIT; int bit_count = bits - 1; //-1 for shifting (32 bits -> shift by 31 to get the first one) for(; bit_count >= 0; --bit_count) if((in & (1 << bit_count)) != 0) break; //now bit_count is at the first binary 0 return bits - bit_count - 1; } size_t utils::binary::GetLeadingOnesCount(char in) //returns the number of leading ones in binary { int bits = sizeof(in) * CHAR_BIT; int bit_count = bits - 1; //-1 for shifting (8 bits -> shift by 7 to get the first one) for(; bit_count >= 0; --bit_count) if((in & (1 << bit_count)) == 0) break; //now bit_count is at the first binary 0 return bits - bit_count - 1; } char_t encoding::MultiCharFromUTF16To32(char16_t high, char16_t low) //private { char_t high_decoded, low_decoded; high_decoded = high - 0xD800; low_decoded = low - 0xDC00; return ((high_decoded << 10) + low_decoded) + 0x10000; } //corresponding from http://floern.com/webscripting/is-utf8-auf-utf-8-pr%C3%BCfen bool encoding::IsUTF8(const std::string &in) { //check for UTF-8 Byte-Order-Mark (not always there if(in.substr(0,3) == "\xEF\xBB\xBF") return true; size_t length = in.length(); for(size_t i = 0; i < length; ++i) { char_t val = in[i]; size_t n = 0; if(val < 0x80) continue; // 0bbbbbbb else if((val & 0xE0) == 0xC0 && val > 0xC1) n = 1; // 110bbbbb (excl C0-C1) else if((val & 0xF0) == 0xE0) n = 2; // 1110bbbb else if((val & 0xF8) == 0xF0 && val < 0xF5) n = 3; // 11110bbb (excl F5-FF) else return false; // invalid UTF-8-character for(size_t c = 0; c < n; c++) // n following bytes? // 10bbbbbb if(++i == length || (in[i] & 0xC0) != 0x80) return false; // invalid UTF-8-character } return true; //no invalid UTF-8 sign found } encoding::Type encoding::GetEncoding(const std::string &in) { //check for UTF-32 before UTF-16 because UTF16_BE would match UTF32_BE but not reverse if(in.substr(0, 4) == std::string("\xFF\xFE\x00\x00", 4)) return encoding::UTF32_LE; else if(in.substr(0, 4) == std::string("\x00\x00\xFE\xFF", 4)) return encoding::UTF32_BE; else if(in.substr(0, 2) == "\xFF\xFE") return encoding::UTF16_LE; else if(in.substr(0, 2) == "\xFE\xFF") return encoding::UTF16_BE; else if(encoding::IsUTF8(in)) return encoding::UTF8; else return encoding::ASCII; } //you firstly have to get the encoding of the string and then convert case-specific str_t encoding::FileStringToUTF(const std::string &str, const std::string &filename) { //All Bytes of UTF-16/32 are stored separately in characters of the str_t by ifstr_t //unify them encoding::Type str_encoding = encoding::GetEncoding(str); str_t ret; if(str_encoding == encoding::ASCII) //Error: we can not know/convert the codepage { C_OUT << tr(_("Fatal error in file")) << _(" \"") << filename << _(":\n"); C_OUT << tr(_("Please save your File in an UTF-Encoding or don't use special letters")) << _("\n"); return str_t(); } else if(str_encoding == encoding::UTF32_BE) { encoding::UTF32BETo32(str, ret); } else if(str_encoding == encoding::UTF32_LE) { encoding::UTF32LETo32(str, ret); } else if(str_encoding == encoding::UTF16_BE) { encoding::UTF16BETo32(str, ret); } else if(str_encoding == encoding::UTF16_LE) { encoding::UTF16LETo32(str, ret); } else if(str_encoding == encoding::UTF8) { encoding::UTF8To32(str, ret); } return str_t(str.begin(), str.end()); } void encoding::UTF16LETo32(const std::string &in, str_t &out) { //Encoding: 2 1 [4 3] //Next Byte Detector: 0xD800 <= x <= 0xDBFF //see http://en.wikipedia.org/wiki/UTF-16/UCS-2 if((((in.size()-2)) % 2) != 0) Error(_("encoding::UTF16LETo32()->Wrong Byte number")); out.reserve((in.size()-2) / 4); //minimum size, is probably higher, will be dynamically increased size_t in_pos = 2; //after BOM unsigned long tmp = 0; char16_t first, second; //first and second 16-Bit-Char, second only sometimes needed while(in_pos < in.size()) { first = static_cast<unsigned char>(in[in_pos]); first |= static_cast<char_t>(static_cast<unsigned char>(in[in_pos+1])) << CHAR_BIT; if(first >= 0xD800 && first <= 0xDBFF) //a second 16-Bit-Character is needed for the one UTF-32 character { in_pos += 2; if(in_pos >= in.size()) Error(_("encoding::UTF16LETo32()->No second 16-Bit char")); second = static_cast<unsigned char>(in[in_pos]); second |= static_cast<char_t>(static_cast<unsigned char>(in[in_pos+1])) << CHAR_BIT; tmp = encoding::MultiCharFromUTF16To32(first, second); } else { tmp = first; } out += tmp; in_pos += 2; } } void encoding::UTF16BETo32(const std::string &in, str_t &out) { //Encoding: 1 2 [3 4] //Next Byte Detector: 0xD800 <= x <= 0xDBFF //see http://en.wikipedia.org/wiki/UTF-16/UCS-2 if((((in.size()-2)) % 2) != 0) Error(_("encoding::UTF16BETo32()->Wrong Byte number")); out.reserve((in.size()-2) / 4); //minimum size, is probably higher, will be dynamically increased size_t in_pos = 2; //after BOM unsigned long tmp = 0; char16_t first, second; //first and second 16-Bit-Char, second only sometimes needed while(in_pos < in.size()) { first = static_cast<unsigned char>(in[in_pos+1]); first |= static_cast<char_t>(static_cast<unsigned char>(in[in_pos])) << CHAR_BIT; if(first >= 0xD800 && first <= 0xDBFF) //a second 16-Bit-Character is needed for the one UTF-32 character { in_pos += 2; if(in_pos >= in.size()) Error(_("encoding::UTF16BETo32()->No second 16-Bit char")); second = static_cast<unsigned char>(in[in_pos+1]); second |= static_cast<char_t>(static_cast<unsigned char>(in[in_pos])) << CHAR_BIT; tmp = encoding::MultiCharFromUTF16To32(first, second); } else { tmp = first; } out += tmp; in_pos += 2; } } void encoding::UTF32LETo32(const std::string &in, str_t &out) { //Encoding: 4 Bytes: 4 3 2 1 if((((in.size()-4)) % 4) != 0) Error(_("encoding::UTF32LETo32()->UTF32_LE->Wrong Byte number")); out.resize((in.size()-4) / 4); size_t in_pos = 4; //after BOM size_t out_pos = 0; unsigned long tmp = 0; while(in_pos < in.size()) { tmp = static_cast<unsigned char>(in[in_pos]); tmp |= static_cast<char_t>(static_cast<unsigned char>(in[in_pos+1])) << CHAR_BIT; tmp |= static_cast<char_t>(static_cast<unsigned char>(in[in_pos+2])) << (CHAR_BIT * 2); tmp |= static_cast<char_t>(static_cast<unsigned char>(in[in_pos+3])) << (CHAR_BIT * 3); out[out_pos] = tmp; ++out_pos; in_pos += 4; } } void encoding::UTF32BETo32(const std::string &in, str_t &out) { //Encoding: 4 Bytes: 1 2 3 4 if((((in.size()-4)) % 4) != 0) Error(_("encoding::UTF32BETo32()->Wrong Byte number")); out.resize((in.size()-4) / 4); size_t in_pos = 4; //after BOM size_t out_pos = 0; while(in_pos < in.size()) { char_t tmp = 0; tmp = static_cast<unsigned char>(in[in_pos+3]); tmp |= static_cast<char_t>(static_cast<unsigned char>(in[in_pos+2])) << CHAR_BIT; tmp |= static_cast<char_t>(static_cast<unsigned char>(in[in_pos+1])) << (CHAR_BIT * 2); tmp |= static_cast<char_t>(static_cast<unsigned char>(in[in_pos])) << (CHAR_BIT * 3); out[out_pos] = tmp; ++out_pos; in_pos += 4; } } void encoding::UTF8To32(const std::string &in, str_t &out) { //see http://www.fileformat.info/info/unicode/utf8.htm //and http://en.wikipedia.org/wiki/UTF-8 //example: //00000000000000100011011010110011 //becomes //11110000 10100011 10011010 10110011 //TODO char_t tmp = 0; size_t in_pos = 0; int following_byte_num = 0, leading_ones = 0; if(in.substr(0,3) == "\xEF\xBB\xBF") in_pos = 3; //BOM, optional out.reserve((in.size() - in_pos) / 4); while(in_pos < in.size()) { leading_ones = utils::binary::GetLeadingOnesCount(in[in_pos]); following_byte_num = leading_ones; //UTF-8 uses e.g. 0b110xxxx for specifying that 1 Byte will follow if(leading_ones > 5) Error(_("encoding::UTF8To32->leading_ones>5")); else if(leading_ones > 1) { following_byte_num -= 1; //= leading_ones - 1 //every following byte can save 6 bits tmp = (in[in_pos] & (0xFF >> (leading_ones+1))) << (following_byte_num * 6); while(following_byte_num > 0) { ++in_pos; --following_byte_num; tmp |= (in[in_pos] & 0x3F) << (following_byte_num * 6); } } else if(leading_ones == 1) Error(_("encoding::UTF8To32->leading_ones==1")); else //leading_ones == 0 { tmp = in[in_pos]; } out += tmp; ++in_pos; } } std::string encoding::UTF32To8(const str_t &str) { std::string ret; int used_bits = 0, needed_following_bytes = 0, used_firstbyte_bits = 0; ret.reserve(str.size() + 3); //the minimum size of the UTF8-return-string + byte order mark ret += "\xEF\xBB\xBF"; for(size_t str_pos = 0; str_pos < str.size(); ++str_pos) { if(str[str_pos] < 0x80) { ret.push_back(str[str_pos]); } else //more UTF8-Bytes are needed in order to encode the character { used_bits = (sizeof(char_t) * 8) - utils::binary::GetLeadingZerosCount(str[str_pos]); needed_following_bytes = (used_bits - 2) / 5; //example: 12 unto 16 bits need 2 following bytes used_firstbyte_bits = 6 - needed_following_bytes; const char first_char_leading_ones = (static_cast<char>(0xFF) << (used_firstbyte_bits+1)); const char first_char_other_content = static_cast<unsigned char>(str[str_pos] >> (needed_following_bytes * 6)); ret.push_back(first_char_leading_ones | first_char_other_content); for(int i = needed_following_bytes; i > 0; --i) { //get the next 6 bits from the left of the string const char_t bits_for_char = ((0x3F << ((i - 1) * 6)) & str[str_pos]) >> ((i-1) * 6); ret.push_back(0x80 | bits_for_char); } } } return ret; } //str is ASCII-Encoded str_t encoding::ToUTF(const std::string &str) { return str_t(str.begin(), str.end()); } std::string encoding::ToASCII(const str_t &str) { size_t length = str.length(); std::string ret; ret.resize(length); for(size_t i = 0; i < length; ++i) { if(str[i] > 0xFF) ret[i] = CHAR_MAX; else ret[i] = str[i]; } return ret; }