Design UTF-8-Lib



  • Hi, ich habe vor eine kleine UTF-8-Bibliothek zu schreiben (ich weiß, dass es das schon gibt 😉 ). Ich dachte mir in etwa das folgende Design:

    - Ein Typedef, der alle UTF-8-Zeichen (32Bit) halten könnte (unsigned long, char32_t oder std::uint32_t).

    - Ein String-Wrapper, der intern nur 8Bit-Zeichen hält, nach außen (operator[], Iteratoren) aber so fungiert, als bestünde er aus 32Bit-Chars (siehe oben).

    - Funktionalität aus <cctype> für diesen Char-Typen (gibt es Look-Up-Tables mit den genauen Codes, damit man die Upper-Zeichen den Lower-Zeichen zuordnen kann? Habe nichts gefunden bei Google...).

    Wie ich das mit den Streams lösen soll ist mir nicht so klar. Ich könnte natürlich i- und ostreams reimplementieren als Wrapper für normale i- und ostreams aber das ist bestimmt nicht optimal. Vorschläge?

    Habe ich etwas Wichtiges vergessen?

    MfG


  • Mod

    IchNutzeComputer schrieb:

    Wie ich das mit den Streams lösen soll ist mir nicht so klar. Ich könnte natürlich i- und ostreams reimplementieren als Wrapper für normale i- und ostreams aber das ist bestimmt nicht optimal. Vorschläge?

    Welche Funktionalität willst du denn in den Streams implementieren? Klingt eher so, als würdest du eine Locale definieren wollen.

    Habe ich etwas Wichtiges vergessen?

    Nicht direkt wichtig, aber ein paar Vorschläge:
    -Prüfung, ob eine gegebene Sequenz gültiges UTF-8 ist.
    -Konvertierung von und zu anderen Unicodestandards
    -Unterstützende Funktionalität für BOM

    Und noch ein wichtiger Vorschlag:
    -Ich fände es deutlich schicker, wenn du nicht deinen eigenen String implementieren würdest, sondern einfach Funktionen, die auf UTF8-Sequenzen arbeiten. Dann muss ich nicht deine Stringimplementierung nutzen (wer weiß, ob die was taugt?), sondern kann meine eigene (bzw. die der Standardbibliothek) nehmen.



  • IchNutzeComputer schrieb:

    - Ein Typedef, der alle UTF-8-Zeichen (32Bit) halten könnte (unsigned long, char32_t oder std::uint32_t).

    Dann kann ich ja gleich UTF-32 verwenden. UTF-8 wurde nämlich erfunden, um nicht so viel Speicher sinnlos mit Nullen zu füllen. 😉



  • @SeppJ
    Locales möchte ich nicht nutzen, mir gefällt das Design von Streams / Locales allgemein nicht (zudem hörte ich, dass diese nicht wirklich performant sind). Ich wollte einfach, dass ich von einem Filestream eben gemischt normale ASCII- und Unicode-Zeichen lesen kann (das ist, wenn ich das richtig verstanden habe, einer der Vorzüge von UTF-8, kann das jemand bestätigen?) und dass ein Umlaut eben auch als ein Zeichen gelesen wird, auch wenn er vielleicht 2 Bytes lang ist in Tat und Wahrheit.

    Das mit dem Prüfen auf Richtigkeit ist meiner Meinung nach sehr wichtig, darf ich keinesfalls vergessen.

    Konvertieren von und zu anderen Standards ist sicherlich etwas Leckeres und Komfortables. Mal schauen wie das mit dem Aufwand aussieht (sorry, kenne mich bei der ganzen Unicode-Geschichte nicht sonderlich gut aus...), das ist ja ein Ein-Mann-Projekt.

    BOM ist ein gutes Stichwort, kannte ich bislang nicht!

    Wegen der String-Sache:
    Ich dachte, dass ich nur einen String-Adapter machen möchte, wo man dann std::string, std::deque oder was auch immer rein tun kann (der wird dann mit char / unsigned char oder so instanziiert). Dann würde ich aber gerne ein schöneres Interface anbieten als std::string und zugleich UTF-8 korrekt unterstützen (wie gesagt; dass es so wirkt als wären lauter 32Bit-Chars drin) (damit auch 4-Byte-Zeichen in der Länge (bei size(), replace(), substr() etc.) und bei den Iteratoren als nur ein einzelnes Zeichen interpretiert werden).

    Alternativ kann ich auch einen String-View machen, der auf Container, Arrays, Streams, Iterator-Paare und Ranges anwendbar ist. Freie Funktionen die mit einer Sequenz arbeiten finde ich persönlich eben nicht ganz so schick (und ist bestimmt auch nicht so performant wenn jedesmal z.B. strlen neu berechnet werden muss), dann doch lieber Views. Was meinst du?

    @dachschaden
    Wie kann ein Typ wahlweise 8 / 16 / 24 / 32 Bit groß sein? Nur mit Polymorphie, was noch langsamer ist und zusätzliche konstante Speicherkosten mit sich bringt. Ich sagte ja, dass der UTF-8-String intern 8Bit-Chars speichert und dieser große Char nur der Kommunikation (zwischen Funktionen) / Verarbeitung dient.



  • IchNutzeComputer schrieb:

    Wie kann ein Typ wahlweise 8 / 16 / 24 / 32 Bit groß sein?

    So funktioniert nunmal UTF-8

    IchNutzeComputer schrieb:

    Ich sagte ja, dass der UTF-8-String intern 8Bit-Chars speichert

    Und wie willst du dann die 2, 3 oder 4 Byte langen UTF-8 zeichen speichern?



  • tkausl schrieb:

    IchNutzeComputer schrieb:

    Wie kann ein Typ wahlweise 8 / 16 / 24 / 32 Bit groß sein?

    So funktioniert nunmal UTF-8

    UTF-8 gibt 8 / 16 / 24 / 32 Bit-Sequenzen vor. Wie man diese speichert (das wird impliziert von "Typ"), hat nichts mit dem Standard zu tun. Und wenn man einen Typ für ein UTF-8-Zeichen machen möchte, dann muss man eben 32Bit nehmen (wie ich anfangs sagte). Alles andere ist in C++ nicht realistisch.

    tkausl schrieb:

    IchNutzeComputer schrieb:

    Ich sagte ja, dass der UTF-8-String intern 8Bit-Chars speichert

    Und wie willst du dann die 2, 3 oder 4 Byte langen UTF-8 zeichen speichern?

    Mit 2, 3 oder 4 Bytes? Die werden vom Wrapper eben speziell interpretiert und behandelt...



  • Edit zu oben:
    *Mit 2, 3 oder 4 Chars?

    Und ich will diesen 32Bit-Datentypen ja nur verwenden, wenn man ein einzelnes Zeichen handhabt (was man meistens nicht tut, stattdessen nutzt man Strings). Und da geht es wirklich nicht anders (in Strings habe ich ja vor, 8Bit-Chars zu verwenden wie gesagt).



  • IchNutzeComputer schrieb:

    - Ein String-Wrapper, der intern nur 8Bit-Zeichen hält, nach außen (operator[], Iteratoren) aber so fungiert, als bestünde er aus 32Bit-Chars (siehe oben).

    Wie soll das funktionieren?



  • TyRoXx schrieb:

    IchNutzeComputer schrieb:

    - Ein String-Wrapper, der intern nur 8Bit-Zeichen hält, nach außen (operator[], Iteratoren) aber so fungiert, als bestünde er aus 32Bit-Chars (siehe oben).

    Wie soll das funktionieren?

    Z.B. mit Proxies. Wird ein 'a' durch ein spezielles 4Byte-Zeichen ersetzt via einem Iterator, dann werden 3 Bytes im String eingefügt und das Zeichen wird gespeichert. Wenn die Performance zu sehr darunter leidet, dann nutzt man darunter std::vector oder std::deque, die immer schön große Blöcke allozieren.



  • laut Wikipedia wird UTF-8 mit 8bit code-UNITS gespeichert
    d.h. zwischen 1-n bytes pro Zeichen (aka code-POINT) - und UTF-8 kann bis dato 1112064 code-POINTS (also Zeichen) abbilden

    und du willst jetzt die code-UNITs per [] zugreifebar machen - z.B. fuer ASCII-Parties - aber intern die n-bytes per code-POINT in einem int32 halten - oder?
    was den Speichervorteil von UTF8 ja irgendwie vernichtet

    btw: eine vector/list oder sonstiges ist für String-Behandlung trotzdem noch viel zu langsam - immer so viel Heap-Parties 🙂

    schau dir doch mal (nur aus spass) http://utfcpp.sourceforge.net/ an



  • Ich würde eine Klasse Char schreiben, welches ein Unicode-Zeichen enthält. Die Klasse ist dann natürlich 32 Bit groß. Dann bekommst Du mit typedef std::basic_string<Char> UnicodeString einen Unicode String. Separate Konvertierfunktionen von utf-8 und nach utf-8 machen dann den Rest.

    Nachteil ist, dass der String mehr Speicher verbraucht. Dafür ist die Verarbeitung sehr viel einfacher. Das 3. Zeichen ist dann das 3. Element im String und nicht je nach dem was im 1. oder 2. Byte steht.

    Übrigens haben wir das in etwa so in unseren cxxtools gemacht.



  • Es gibt zwei unterschiedliche Teile von Unicode-Handling:
    - Encoden/Decoden von verschiedenen Zeichensätzen auf Basis von Codepoints (UTF-8, UTF-32, etc.), mit allem was dazu gehört (Fehlererkennung/-behandlung, BOM, etc.)
    - Stringoperationen auf Grapheme-Clusters, z.B. lowercase, Anzahl Zeichen, Unicode Normalisierung durchführen, Leerzeichen erkennen, etc.

    Der erste Punkt ist relativ einfach, der zweite sehr sehr schwierig. Die Standardbibliothek macht das falsch und die cxxtools von tntnet daher ebenfalls (weil isupper z.B. nicht nur durch den Codepoint bestimmt werden kann).

    Auf Basis von Codepoints zu operieren ist nicht mehr zeitgemäss. Uppercase von "ß" ist "SS". Codepoints sind nicht gleich Buchstaben (Ç=C+◌̧ ). Zudem sind alle Operationen sprachenabhängig.

    Daher mein Rat: Beschränke dich auf den ersten Teil und lasse den zweiten bleiben. Besser nicht, als falsch.



  • Danke unicoder für den besseren Einblick. Ich hätte nicht erwartet, dass Unicode alleine eine solche Komplexität aufweist. Ursprünglich wollte ich Dateien in UTF-8 auslesen, verschiedene Transformationen machen (Uppercase <-> Lowercase), verschiedene Informationen sammeln (ob an Stelle so und soviel ein ö oder sowas ist) und letztendlich die Datei (modifiziert) in UTF-8 wieder abspeichern. Wenn ich dich richtig verstanden habe, dann ist schon das ein zu schwieriges Vorhaben.



  • IchNutzeComputer schrieb:

    Ursprünglich wollte ich Dateien in UTF-8 auslesen, verschiedene Transformationen machen (Uppercase <-> Lowercase), verschiedene Informationen sammeln (ob an Stelle so und soviel ein ö oder sowas ist) und letztendlich die Datei (modifiziert) in UTF-8 wieder abspeichern.

    Was soll denn mit dem Ergebnis passieren? Vielleicht reicht eine naive Herangehensweise.



  • Das Ergebnis wird später wieder vom Programm selbst und von anderen UTF-8-fähigen Programmen gelesen und weiterverarbeitet. Es handelt sich je Datei größtenteils um gewöhnlichen Text. Der Text wird auf Buchstabenhäufigkeiten / Wörter (hier möchte ich noch ein paar Sondertricks einbauen, um u.U. Wortstämme herauszufiltern) geprüft. Meinem Wissen nach nutzt keine Sprache außer Latein selbst bloß das lateinische Alphabet ohne zusätzliche Sonderzeichen, weswegen ich das hier mit UTF-8 machen möchte. Ich möchte nichts Fertiges nehmen und mein Programm dann kurz zusammenbasteln, sondern möchte auch zum ersten Mal in Kontakt mit Unicode kommen, weswegen ich die genannten Aspekte umsetzen möchte (auch wenn ich mir wegen dem Design noch unklar bin).


Anmelden zum Antworten