WideString, AnsiString, UTF8String, UCS4String Verwirrung

Für Fragen zur Programmiersprache auf welcher Lazarus aufbaut
Antworten
Benutzeravatar
theo
Beiträge: 10869
Registriert: Mo 11. Sep 2006, 19:01

Re: WideString, AnsiString, UTF8String, UCS4String Verwirrung

Beitrag von theo »

@Michael: Du warst schneller, aber hier trotzdem noch mein getippse (wär ja schad drum ;-)

UTF-16 kann man nicht in UCS-2 konvertieren, da UCS-2 nur Zeichen bis $FFFF berücksichtigt.
Umgekehrt braucht es keine Konvertierung, da UCS-2 ein Subset von UTF-16 ist. Deshalb sagte ich UTF-16 reicht. ;-)
Man kann aber einen UCS-2 Char (WideChar) abholen. Das klappt mit grösster Wahrscheinlichkeit (höchstens in China nicht).
Falls dort tatsächlich ein Surrogat Paar steht, kann man eine Exception auslösen.
Zwischen UTF-32 und UCS-4 gibt es afaics keinen Unterschied.

mschnell
Beiträge: 3444
Registriert: Mo 11. Sep 2006, 10:24
OS, Lazarus, FPC: svn (Window32, Linux x64, Linux ARM (QNAP) (cross+nativ)
CPU-Target: X32 / X64 / ARMv5
Wohnort: Krefeld

Re: WideString, AnsiString, UTF8String, UCS4String Verwirrung

Beitrag von mschnell »

theo hat geschrieben:UTF-16 kann man nicht in UCS-2 konvertieren, da UCS-2 nur Zeichen bis $FFFF berücksichtigt.
Ist natürlich richtig, wenn man "konvertieren" als "verlustfrei konvertieren" interpretiert.
Man kann aber eine Konvertierung versuchen:
z.B.
- Falls keine surrogate pairs auftreten ist die Konvertierung trivial.
- Falls surrogate pairs auftreten kann man entweder eine Exception auslösen oder (fände ich besser) die nicht konvertierbaren Zeichen durch ein sonst nicht vorkommendes ErsatzZeichen (z.B. #$FFFF) ersetzen und eine "error" property auf true setzen.

(Dasselbe gilt entsprechend bei anderen nicht verlustfrei durchführbaren Konvertierungen wie z.B. -> ANSIString.)

-Michael

Benutzeravatar
theo
Beiträge: 10869
Registriert: Mo 11. Sep 2006, 19:01

Re: WideString, AnsiString, UTF8String, UCS4String Verwirrung

Beitrag von theo »

mschnell hat geschrieben: Ist natürlich richtig, wenn man "konvertieren" als "verlustfrei konvertieren" interpretiert.
Man kann aber eine Konvertierung versuchen:
Absolut einverstanden.
mschnell hat geschrieben: - Falls keine surrogate pairs auftreten ist die Konvertierung trivial.
Ja. Sprich: eine Nullnummer, dann passiert nämlich gar nix.

Benutzeravatar
theo
Beiträge: 10869
Registriert: Mo 11. Sep 2006, 19:01

Re: WideString, AnsiString, UTF8String, UCS4String Verwirrung

Beitrag von theo »

Ich hab mal heute abend mal was gebastelt. Siehe Anhang.

Damit kann man vor allem UTF8Strings leichter als Pseudo Buchstaben ansprechen.
Ist noch wenig getestet und da könnte man noch mehr machen, falls das überhaupt sinnvoll ist.

Beispiel:

Code: Alles auswählen

procedure TForm1.Button1Click(Sender: TObject);
var Unic:TUnicodeClass;
i:integer;
begin
  Unic:=TUnicodeClass.Create('Schön ist es auf der Welt zu sein. Ääh, oder so ;-)');
  for i:=1 to Unic.Count do if Unic[i]='ä' then Unic[i]:='o';
  Unic.LowerCase;
  Memo1.text:=Unic.UniString;
  Unic.free;
end;
EDIT:
Habe eine verbesserte Version angehängt und die Sache umbenannt, da es doch hauptsächlich um UTF-8 geht.
Dateianhänge
utf8helper.pas
Neue Version a
(8.03 KiB) 93-mal heruntergeladen
Zuletzt geändert von theo am Do 23. Okt 2008, 22:14, insgesamt 5-mal geändert.

RSE
Beiträge: 462
Registriert: Mi 30. Jul 2008, 13:11
OS, Lazarus, FPC: WinXP SP3 (L 0.9.28.2 FPC 2.2.4)
CPU-Target: 32Bit
Kontaktdaten:

Re: WideString, AnsiString, UTF8String, UCS4String Verwirrung

Beitrag von RSE »

mschnell hat geschrieben:Wenn Du spezielle utf-16 Funktionen einbauen willst (die möglicherweise den Datentyp widestring zum Speichern der Information verwenden), brauchst Du natürlich einen Konverter utf8utf16utf32. Das sollte relativ einfach gehen, weil keine Tabellen benötigt werden sondern ein Algorithmus, der sich aus der Unicode Definition ergibt
Könntest du so etwas bauen? Das wär super. Dieser Thread ist für mich (nahezu) der erste Kontakt mit Unicode, daher würde ich das ungern selbst machen.
mschnell hat geschrieben:Die Anzahl der "sichtbaren" Zeichen sollte doch für alle Codierungen gleich sein. Da reicht doch eine Funktion ????
(Ich vermute "??Count" ist die Anzahl der gleichgroßen Elemente, die je nach Zeichen unterschiedlich sein kann)
Hm, du hast Recht. Welche Codierung intern vorliegt, muss sowieso automatisch geregelt werden. Das trifft auf Length und auf Count zu.
mschnell hat geschrieben:Ist natürlich richtig, wenn man "konvertieren" als "verlustfrei konvertieren" interpretiert.
Genau das tue ich nicht. Man braucht also desweiteren folgende Funktion:

Code: Alles auswählen

...
  function AnsiSuitable(ACodePage): Boolean;  // wie schon in der letzten Version
  function UCS2Suitable: Boolean;  // Test, ob Umwandlung in UCS2 verlustfrei möglich ist
UTF-8, UTF-16, UTF-32 und UCS-4 können ja sowieso alle Zeichen darstellen (wenn man den eventuellen Unterschied zw. UTF-32 und UCS-4 vernachlässigt).
theo hat geschrieben:Ich hab mal heute abend mal was gebastelt.
Du speicherst grundsätzlich in UTF-8. Ich würde da eher einen anderen Weg verfolgen. Ich würde grundsätzlich Variablen für die Speicherung in allen unterstützten Formatierungen deklarieren, und diese dann füllen, sobald die Umwandlung in diejenige Formatierung sowieso erfolgen muss. Quasi so:

Code: Alles auswählen

TStr = class(TObject)
private
  AnsiStr: AnsiString;
  UTF8Str: AnsiString;
  UTF16Str: WideString;
  UCS4Str: UCS4String;
 
  AnsiStrSet: Boolean;  // true, wenn AnsiStr den wahren Wert hat, mit false initialisiert
  UTF8StrSet: Boolean;  // true, wenn UTF8Str den wahren Wert hat, mit false initialisiert
  UTF16StrSet: Boolean;  // true, wenn UTF16Str den wahren Wert hat, mit false initialisiert
  UCS4StrSet: Boolean;  // true, wenn UCS4Str den wahren Wert hat, mit false initialisiert
public
  ...
Die Implementierung von GetUTF16String würde dann z.B. so aussehen:

Code: Alles auswählen

function TStr.GetUTF16String: WideString;
  result := '';  // oder muss result := UTF8Decode(''); ?
  if UTF16StrSet then begin
    result := UTF16Str;
    exit;
  end;
  if UTF8StrSet then begin
    UTF16Str := UTF8Decode(UTF8Str);
    UTF16StrSet := true;
    result := UTF16Str;
    exit;
  end;
  if UCS4StrSet then begin
    UTF16Str := UCS4StringToWideString(UCS4Str);
    UTF16StrSet := true;
    result := UTF16Str;
    exit;
  end;
  if AnsiStrSet then begin
    UTF8Str := AnsiToUTF8(AnsiStr);
    UTF8StrSet := true;
    UTF16Str := UTF8Decode(UTF8Str);
    UTF16StrSet := true;
    result := UTF16Str;
    exit;
  end;
Wenn ein String manipuliert wird (Schreibzugriff auf einen Buchstaben), dann muss jeder bis dahin gesetzte String manipuliert werden, um die Konsistenz zu wahren. Das heißt auch, dass eine Umwandlung immer zumindest die Anzahl der sichtbaren Zeichen wahren muss, aber ich denke, dass das sowieso der Fall ist.
Seit er seinen neuen Computer hat, löst er alle Probleme, die er vorher nicht hatte!

mschnell
Beiträge: 3444
Registriert: Mo 11. Sep 2006, 10:24
OS, Lazarus, FPC: svn (Window32, Linux x64, Linux ARM (QNAP) (cross+nativ)
CPU-Target: X32 / X64 / ARMv5
Wohnort: Krefeld

Re: WideString, AnsiString, UTF8String, UCS4String Verwirrung

Beitrag von mschnell »

RSE hat geschrieben:Du speicherst grundsätzlich in UTF-8. Ich würde da eher einen anderen Weg verfolgen. Ich würde grundsätzlich Variablen für die Speicherung in allen unterstützten Formatierungen deklarieren, und diese dann füllen, sobald die Umwandlung in diejenige Formatierung sowieso erfolgen muss.
Wenn man ein bestimmtes Datenformat für die interne Speicherung verwendet, wird die unit weniger chaotisch.
UTF-8 ist dann am platzsparendsten aber auch am langsamsten. Ich fände UCS4 besser (weil einfacher und schneller), braucht aber wesentlich mehr platz (4 mal so viel bei reinem ASCII-Text.
-Michael

Benutzeravatar
theo
Beiträge: 10869
Registriert: Mo 11. Sep 2006, 19:01

Re: WideString, AnsiString, UTF8String, UCS4String Verwirrung

Beitrag von theo »

Dass man nur einen String hält ist für mich klar.
Ich habe mich für UTF-8 entschieden weil:
- Die LCL damit arbeitet, man muss also erstmal nicht unbedingt umwandeln.
- Weil UTF-8 den ganzen Unicode-Range halten kann und dabei Speicher spart gegenüber UCS-4.
- Weil die Herausforderung war, UTF-8 Strings "Buchstabenweise" anzusprechen. Für die Konvertierung nach WideString und dem arbeiten damit bräuchte es ja keine extra Klasse. Damit kann man im Editor wieder "wie gewohnt" arbeiten, auch mit Umlauten:
z.B. for i:=1 to Unic.Count do if Unic='ä' then Unic:='o';

mschnell
Beiträge: 3444
Registriert: Mo 11. Sep 2006, 10:24
OS, Lazarus, FPC: svn (Window32, Linux x64, Linux ARM (QNAP) (cross+nativ)
CPU-Target: X32 / X64 / ARMv5
Wohnort: Krefeld

Re: WideString, AnsiString, UTF8String, UCS4String Verwirrung

Beitrag von mschnell »

theo hat geschrieben:z.B. for i:=1 to Unic.Count do if Unic='ä' then Unic:='o';

DAS wird aber langsam !!! in jedem Schrit zweimal den string durchsuchen bis das i-te Zeichen gefunden ist :(.

BTW.: Was wird denn aus der Zeile
Unic='ä';
???
Wie wir um anderen Thread gesehen haben wird eine String-Konstante als Unicode behandelt 'ä' ist also vermutlich ein String (mit zwei ERlementen) und kein char.
Die Array-Property muss also von Typ tuf8String sein.
Was macht man denn wenn man einen WideChar einsetzen will ? "overload" mit verschiedenen Typen gibt es doch bei Properties nicht ? oder doch ?

-Michael

Benutzeravatar
theo
Beiträge: 10869
Registriert: Mo 11. Sep 2006, 19:01

Re: WideString, AnsiString, UTF8String, UCS4String Verwirrung

Beitrag von theo »

mschnell hat geschrieben: DAS wird aber langsam !!! in jedem Schrit zweimal den string durchsuchen bis das i-te Zeichen gefunden ist :(.
Schau dir den Code doch mal an. Beim sequenziellen Scannen wird immer ein Index auf die letzte Position gespeichert.
also i, i+1, i+2 liest nur immer den nächsten Buchstaben ein ausgehend vom vorherigen.
Das kann man noch verbessern, aber grundsätzlich ist das relativ schnell.
mschnell hat geschrieben: Was macht man denn wenn man einen WideChar einsetzen will ? "overload" mit verschiedenen Typen gibt es doch bei Properties nicht ? oder doch ?
Schau dir den Code doch mal an.
Es gibt nat. nur EIN default property. Die sind so definiert:

property UnicodeChars[Index: Integer]: UCS4Char read GetUCS4Char write PutUCS4Char;
property Utf8Chars[Index: Integer]: UTF8String read GetUTF8Char write PutUTF8Char; default;
property Utf16Chars[Index: Integer]: WideString read GetUTF16Char write PutUTF16Char;
property Ucs2Chars[Index: Integer]: WideChar read GetUCS2Char write PutUCS2Char;

mschnell
Beiträge: 3444
Registriert: Mo 11. Sep 2006, 10:24
OS, Lazarus, FPC: svn (Window32, Linux x64, Linux ARM (QNAP) (cross+nativ)
CPU-Target: X32 / X64 / ARMv5
Wohnort: Krefeld

Re: WideString, AnsiString, UTF8String, UCS4String Verwirrung

Beitrag von mschnell »

theo hat geschrieben: Beim sequenziellen Scannen wird immer ein Index auf die letzte Position gespeichert.
Hast Du schon Tricks eingebaut :) :) :) Prima !
theo hat geschrieben: Es gibt nat. nur EIN default property.
Hatte ich mir gedacht, dass sich das nicht besser machen läßt. Schade !
-Michael

RSE
Beiträge: 462
Registriert: Mi 30. Jul 2008, 13:11
OS, Lazarus, FPC: WinXP SP3 (L 0.9.28.2 FPC 2.2.4)
CPU-Target: 32Bit
Kontaktdaten:

Re: WideString, AnsiString, UTF8String, UCS4String Verwirrung

Beitrag von RSE »

Beim halten nur eines Strings bedarf es bei jedem einzelnen Zugriff einer (langsamen) Konvertierung, falls er nicht gerade in der gleichen Codierung stattfindet, die gespeichert ist. Hält man mehrere Strings, die erst gesetzt werden, wenn sie gebraucht werden, dann werden die Umwandlungen minimiert, der Platzverbrauch erhöht sich erst, wenn eine Umwandlung tatsächlich benötigt wird. Arbeitet man z.B. nur mit UTF-8, wird auch nur ein UTF-8 String gespeichert, die anderen Strings bleiben leer.

Die Sache mit dem Merken des zuletzt gefragten Index ist gar nicht mal übel, es kommt ja sicherlich nicht selten vor, dass der String durchlaufen wird.
Seit er seinen neuen Computer hat, löst er alle Probleme, die er vorher nicht hatte!

RSE
Beiträge: 462
Registriert: Mi 30. Jul 2008, 13:11
OS, Lazarus, FPC: WinXP SP3 (L 0.9.28.2 FPC 2.2.4)
CPU-Target: 32Bit
Kontaktdaten:

Re: WideString, AnsiString, UTF8String, UCS4String Verwirrung

Beitrag von RSE »

Wie konkateniert man UCS4Strings? Folgendes funktioniert nicht:

Code: Alles auswählen

var
  s1, s2: UCS4String;
  ...
  s1 := s1+s2; // Konkatenation mit Operator "+" funktioniert nicht (Operator "+" not overloaded)
  s1 := concat(s1,s2); // Konkatenation mit concat() funktioniert nicht (ShortString erwartet)
Seit er seinen neuen Computer hat, löst er alle Probleme, die er vorher nicht hatte!

Benutzeravatar
theo
Beiträge: 10869
Registriert: Mo 11. Sep 2006, 19:01

Re: WideString, AnsiString, UTF8String, UCS4String Verwirrung

Beitrag von theo »

RSE hat geschrieben:Wie konkateniert man UCS4Strings? Folgendes funktioniert nicht:
Wenn du dir die Definition anschaust siehts du, dass es kein eigentlicher String Typ ist sondern ein Dynamisches Array.
Das muss man halt gewissermassen "von Hand" umkopieren.

RSE
Beiträge: 462
Registriert: Mi 30. Jul 2008, 13:11
OS, Lazarus, FPC: WinXP SP3 (L 0.9.28.2 FPC 2.2.4)
CPU-Target: 32Bit
Kontaktdaten:

Re: WideString, AnsiString, UTF8String, UCS4String Verwirrung

Beitrag von RSE »

Im Anhang findet ihr die komplette Unit StrClass, hier die Klassendeklaration:

Code: Alles auswählen

TStr = class
  private
    AnsiStr: AnsiString;  // wird nur gespeichert, wenn es gesetzt oder gelesen wird
    UTF8Str: AnsiString;  // wird nur gespeichert, wenn es gesetzt oder gelesen wird
    UTF16Str: WideString;  // wird nur gespeichert, wenn es gesetzt oder gelesen wird
    UCS4Str: UCS4String;  // wird nur gespeichert, wenn es gesetzt oder gelesen wird
 
    AnsiStrSet: Boolean;  // true, wenn AnsiStr gesetzt ist, mit false initialisiert
    UTF8StrSet: Boolean;  // true, wenn UTF8Str gesetzt ist, mit false initialisiert
    UTF16StrSet: Boolean;  // true, wenn UTF16Str gesetzt ist, mit false initialisiert
    UCS4StrSet: Boolean;  // true, wenn UCS4Str gesetzt ist, mit false initialisiert
 
    function GetAnsiString: AnsiString;  // AnsiStr setzen, falls ein anderer String bereits gesetzt ist
    function GetUTF8String: UTF8String;  // UTF8Str setzen, falls ein anderer String bereits gesetzt ist
    function GetUTF16String: WideString;  // UTF16Str setzen, falls ein anderer String bereits gesetzt ist
    function GetUCS4String: UCS4String;  // UCS4Str setzen, falls ein anderer String bereits gesetzt ist
 
    procedure SetAnsiString(const AValue: AnsiString);  // AnsiStr setzen, alle anderen leeren
    procedure SetUTF8String(const AValue: UTF8String);  // UTF8Str setzen, alle anderen leeren
    procedure SetUTF16String(const AValue: WideString);  // UTF16Str setzen, alle anderen leeren
    procedure SetUCS4String(const AValue: UCS4String);  // UCS4Str setzen, alle anderen leeren
 
    function GetAnsiSubStr(AStartPosition, ACharacterCount: Cardinal): AnsiString;
    function GetUTF8SubStr(AStartPosition, ACharacterCount: Cardinal): UTF8String;
    function GetUTF16SubStr(AStartPosition, ACharacterCount: Cardinal): WideString;
    function GetUCS4SubStr(AStartPosition, ACharacterCount: Cardinal): UCS4String;
 
    procedure SetAnsiSubStr(AStartPosition, ACharacterCount: Cardinal; const AValue: AnsiString);
    procedure SetUTF8SubStr(AStartPosition, ACharacterCount: Cardinal; const AValue: UTF8String);
    procedure SetUTF16SubStr(AStartPosition, ACharacterCount: Cardinal; const AValue: WideString);
    procedure SetUCS4SubStr(AStartPosition, ACharacterCount: Cardinal; const AValue: UCS4String);
 
    function GetAnsiChar(const APosition: Cardinal): Char;
    function GetUTF8Char(const APosition: Cardinal): UTF8String;
    function GetUTF16Char(const APosition: Cardinal): WideString;
    function GetUCS4Char(const APosition: Cardinal): UCS4Char;
 
    procedure SetAnsiChar(const APosition: Cardinal; const AValue: Char);
    procedure SetUTF8Char(const APosition: Cardinal; const AValue: UTF8String);
    procedure SetUTF16Char(const APosition: Cardinal; const AValue: WideString);
    procedure SetUCS4Char(const APosition: Cardinal; const AValue: UCS4Char);
  public
    constructor create;  // alle xxxStrSet auf false setzen
 
    property AnsiString: AnsiString read GetAnsiString write SetAnsiString;
    property UTF8String: UTF8String read GetUTF8String write SetUTF8String;
    property UTF16String: WideString read GetUTF16String write SetUTF16String;
    property UCS4String: UCS4String read GetUCS4String write SetUCS4String;
 
    property AnsiSubStr[AStartPosition, ACharacterCount: Cardinal]: AnsiString
      read GetAnsiSubStr write SetAnsiSubStr;
    property UTF8SubStr[AStartPosition, ACharacterCount: Cardinal]: UTF8String
      read GetUTF8SubStr write SetUTF8SubStr;
    property UTF16SubStr[AStartPosition, ACharacterCount: Cardinal]: WideString
      read GetUTF16SubStr write SetUTF16SubStr;
    property UCS4SubStr[AStartPosition, ACharacterCount: Cardinal]: UCS4String
      read GetUCS4SubStr write SetUCS4SubStr;
 
    property AnsiChar[APosition: Cardinal]: Char read GetAnsiChar write SetAnsiChar;
    property UTF8Char[APosition: Cardinal]: UTF8String read GetUTF8Char write SetUTF8Char;
    property UTF16Char[APosition: Cardinal]: WideString read GetUTF16Char write SetUTF16Char;
    property UCS4Char[APosition: Cardinal]: UCS4Char read GetUCS4Char write SetUCS4Char;
 
    function Count: Cardinal;
    function SubStrLength(const AStartPosition, ACharacterCount: Cardinal): Cardinal;
 
//    property CodePage: ? read GetCodePage write SetCodePage;  // aktuell verwendete Codepage, Voreinstellung ist z.B. die Codepage im System, falls es mit Ansi arbeitet
//    function Suitable(ACodePage: ?): Boolean  // true, wenn der aktuell gespeicherte String mit der übergebenen Codepage komplett dargestellt werden kann.
//    function GetSuitableCodepages: ?;  // Gibt eine Liste aller im System vorhandenen oder sonstwie bekannten Codepages zurück, die den aktuellen String darstellen können
  end;
Die gesamte Klasse ist noch ungetestet, aber dafür sehr akribisch programmiert :D .

Was noch fehlt:
  • In Zeile 235 muss mal bitte noch jemand den UCS4String konkatenieren. Ich hab auch mit dynamischen Arrays null Erfahrung und hab im Netz nichts zur Konkatenation speziell gefunden. Vielleicht liegt´s auch an meiner Müdigkeit... Ich würd da jetzt mit Zeigern rumhantieren, aber irgendwie hab ich Bauchschmerzen dabei.
  • In Zeile 218 benutze ich High(Integer) anstatt von richtigerweise High(Cardinal). Letzteres wird aus irgendwelchen Gründen nicht akzeptiert, entweder kann er High(Cardinal) nicht berechnen, oder die Zahl ist zu groß für copy. Wenn die Zahl für copy zu groß ist, dann würde das überall ersetzt werden müssen, oder Cardinal kann generell auf Integer umgestellt werden, was die maximale Länge der Strings halbieren würde.
  • Der ganze Kram mit den Codepages ist, wie man sieht, noch nicht implementiert.
  • Ich habe nicht getestet, ob der String durch UTF8ToAnsi kürzer werden kann, wenn nicht repräsentierbare Zeichen vorkommen. Ich habe erstmal angenommen nein ;-).
Dateianhänge
strclass.pas
(13.19 KiB) 72-mal heruntergeladen
Seit er seinen neuen Computer hat, löst er alle Probleme, die er vorher nicht hatte!

mschnell
Beiträge: 3444
Registriert: Mo 11. Sep 2006, 10:24
OS, Lazarus, FPC: svn (Window32, Linux x64, Linux ARM (QNAP) (cross+nativ)
CPU-Target: X32 / X64 / ARMv5
Wohnort: Krefeld

Re: WideString, AnsiString, UTF8String, UCS4String Verwirrung

Beitrag von mschnell »

RSE hat geschrieben:Die Sache mit dem Merken des zuletzt gefragten Index ist gar nicht mal übel, es kommt ja sicherlich nicht selten vor, dass der String durchlaufen wird.
Das verfahren kann man natürlich auf einen Index-Cache mit n Einträgen erweitern ...
-Michael

Antworten