Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Algorithmen, Datenstrukturen und Klassendesign (https://www.delphipraxis.net/78-algorithmen-datenstrukturen-und-klassendesign/)
-   -   WideStringlist in UTF-8 mit Delphi 2007 speichern (https://www.delphipraxis.net/213634-widestringlist-utf-8-mit-delphi-2007-speichern.html)

ioster 30. Aug 2023 15:12

WideStringlist in UTF-8 mit Delphi 2007 speichern
 
Moin,

ich habe eine Anwendung, die Textzeilen nach einem vorgegebenen Format in einer Stringlist sammelt und dann in einer Textdatei wegschreibt, die von einem Konverter zur Weiterverarbeitung abgegriffen wird.

Nun stand eine Änderung an, weil in den Strings Zeichen aus dem erweiterten Zeichensatz verarbeitet werden sollten. Konkret geht es um slowakische Sonderzeichen.

Deswegen musste ich die Routinen auf Widestrings und eine Widestringlist umbauen. Funktioniert inzwischen so weit auch wunderbar, ABER der Konverter streikt, da die neuen Textdateien in UTF-16-LE und nicht mehr in UTF-8 codiert werden.

Ich nutze zur Speicherung die Methode SaveToFile, die leider keine Optionen in Sachen Codierung bietet. Anhand der Sourcen konnte ich auch nur ermitteln, dass beim Aufruf der Methode ein FileStream zur Speicherung genutzt wird.

Delphi-Quellcode:
procedure TWideStrings.SaveToFile(const Filename: WideString);
var
  Stream: TStream;
begin
  Stream := TFileStream.Create(Filename, fmCreate);
  try
    SaveToStream(Stream);
  finally
    Stream.Free;
  end;

procedure TWideStrings.SaveToStream(Stream: TStream);
var
  S: WideString;
begin
  S := GetTextStr;
  Stream.WriteBuffer(Pointer(S)^, Length(S) * SizeOf(WideChar));
end;
Ich gehe davon aus, dass die UTF-Codierung irgendwo an einer anderen Stelle festgelegt ist.

Hat jemand eine Idee, wie ich mit einer ähnlichen Funktion die Codierung des Outputs selber steuern kann? Alles was ich gefunden habe, passte nicht zu Delphi 2007.

Danke im Voraus.
Ingo

himitsu 30. Aug 2023 15:23

AW: WideStringlist in UTF-8 mit Delphi 2007 speichern
 
Wenn UTF-8, dann kann das auch eine normale TStringList (nur das BOM, falls nötig, mußt du einfach als erstes Zeichen in die erste Zeile einfügen).

Delphi-Referenz durchsuchenTEncoding, in aktuelleren Delphis,
aber hier geht auch MSDN-Library durchsuchenWideCharToMultiByte mit CP_UTF8
oder eine der vielen UTF8-Konvertierungs-Funktionen aus der System.pas


PS: Der Delphi-Referenz durchsuchenUTF8String ist einfach ein AnsiString, mit entsprechender CodePage.

ioster 31. Aug 2023 07:44

AW: WideStringlist in UTF-8 mit Delphi 2007 speichern
 
Moin,

ich kann in dem Projekt nur auf die Möglichkeiten von Delphi 2007 zurückgreifen. Anhand von Foreneinträgen und technischen Tipps habe ich schon festgestellt, dass sich der Hersteller inzwischen einiges mehr zu dem Thema einfallen lassen hat.

Eine TStringlist habe ich bisher genutzt. Doch damit komme ich nicht weiter, wenn ich den erweiterten Zeichensatz verarbeiten möchte. Den kann ich nur mit WideStrings auslesen und ich wurde auch schon gerüffelt, als ich bei den Kollegen nachfragte, warum denn die Sonderzeichen bei der Zuweisung in die TStringlist wieder verschwinden würden. Daran merkt man, dass ich bisher mit den WideStrings in der Form nichts zu tun hatte.

Jetzt habe ich gestern versucht, die WideStringliste Zeile für Zeile mit einem Filestream abzuspeichern, habe dabei aber nichts Brauchbares erzeugen können. Wenn ich die Textdatei mit einem Editor öffne, bekomme ich Sonderzeichen aus dem südostasiatischen Raum angezeigt - vermutlich chinesisch.

Ich möchte zusätzlich auch den BOM mitgeben, weil es der Konverterhersteller sich so wünscht. Aus Codebeispielen habe ich versucht, mir eine eigene Prozedur zusammenzustellen, die so aussieht:

Delphi-Quellcode:
procedure SaveToUTF16(const Filename: string; Content: TWidestringlist);
var
  BOM    : WideChar;
  Stream : TFileStream;
  Zaehler : integer;
  Zeile  : WideString;
begin
  Stream := TFileStream.Create(Filename, fmCreate);
 
  try
    BOM := WideChar($FFFE);
    Stream.WriteBuffer(BOM, SizeOf(BOM));

    FOR Zaehler := 0 to Content.Count - 1  Do
      begin
        Zeile := WideString(Content[Zaehler] + sLineBreak);
        Stream.WriteBuffer(PWideChar(Zeile)^, Length(Zeile) * SizeOf(WideChar));
      end;
  finally
    Stream.Free;
end;
Ich fürchte nur, dass ich ein Problem bei der Umwandlung oder bei der Positionierung des Streams habe.

Es hat schon seinen Grund, warum ich sonst gerne auf bestehende Methoden wie SaveToFile zurückgreife.

Danke im Voraus.

Viele Grüße
Ingo

shebang 31. Aug 2023 07:54

AW: WideStringlist in UTF-8 mit Delphi 2007 speichern
 
Zitat:

Zitat von ioster (Beitrag 1526282)
Wenn ich die Textdatei mit einem Editor öffne, bekomme ich Sonderzeichen aus dem südostasiatischen Raum angezeigt - vermutlich chinesisch.

Erkennt der Editor denn die von dir gewünschte Kodierung?

Uwe Raabe 31. Aug 2023 08:32

AW: WideStringlist in UTF-8 mit Delphi 2007 speichern
 
In deinem Code speicherst du aber UTF-16 anstatt UTF-8. Ist das denn so gewollt?

Zitat:

Zitat von ioster (Beitrag 1526260)
ABER der Konverter streikt, da die neuen Textdateien in UTF-16-LE und nicht mehr in UTF-8 codiert werden.

Mit dieser kleinen Routine kannst du auch UTF-8 schreiben:
Delphi-Quellcode:
uses
  WideStrings, WideStrUtils;

procedure SaveToUTF8(const Filename: string; Content: TWideStrings);
var
  Stream : TFileStream;
  Zeile : UTF8String;
begin
  Zeile := UTF8Encode(Content.Text);
  Stream := TFileStream.Create(Filename, fmCreate);
  try
    Stream.WriteBuffer(sUTF8BOMString[1], Length(sUTF8BOMString));
    Stream.WriteBuffer(Zeile[1], Length(Zeile));
  finally
    Stream.Free;
  end;
end;

ioster 31. Aug 2023 08:58

AW: WideStringlist in UTF-8 mit Delphi 2007 speichern
 
Liste der Anhänge anzeigen (Anzahl: 2)
Zitat:

Zitat von shebang (Beitrag 1526283)
Zitat:

Zitat von ioster (Beitrag 1526282)
Wenn ich die Textdatei mit einem Editor öffne, bekomme ich Sonderzeichen aus dem südostasiatischen Raum angezeigt - vermutlich chinesisch.

Erkennt der Editor denn die von dir gewünschte Kodierung?

Da ich Textdateien erzeuge, öffne ich sie mit Doppelklick mit dem Editor, der von Microsoft in Windows zur Verfügung gestellt wird. Der unterscheidet die Codierungen.
Anhang 56212
Darüber habe ich auch den Unterschied zwischen den Textdateien aus Stringlist und WideStringlist feststellen können. Mit einem Hex-Editor gehe ich an so etwas nicht heran.

Speichere ich nun Textzeilen mit der oben aufgeführten Routine, dann wird als Codierung UTF-16-BE angezeigt, doch der Dateiinhalt ist alles andere als lesbar.
Anhang 56213
Nach meinem Verständnis müsste dort trotzdem Klartext erkennbar sein.

ioster 31. Aug 2023 09:00

AW: WideStringlist in UTF-8 mit Delphi 2007 speichern
 
Zitat:

Zitat von Uwe Raabe (Beitrag 1526284)
In deinem Code speicherst du aber UTF-16 anstatt UTF-8. Ist das denn so gewollt?

Zitat:

Zitat von ioster (Beitrag 1526260)
ABER der Konverter streikt, da die neuen Textdateien in UTF-16-LE und nicht mehr in UTF-8 codiert werden.

Mit dieser kleinen Routine kannst du auch UTF-8 schreiben:
Delphi-Quellcode:
uses
  WideStrings, WideStrUtils;

procedure SaveToUTF8(const Filename: string; Content: TWideStrings);
var
  Stream : TFileStream;
  Zeile : UTF8String;
begin
  Zeile := UTF8Encode(Content.Text);
  Stream := TFileStream.Create(Filename, fmCreate);
  try
    Stream.WriteBuffer(sUTF8BOMString[1], Length(sUTF8BOMString));
    Stream.WriteBuffer(Zeile[1], Length(Zeile));
  finally
    Stream.Free;
  end;
end;

Der Konverterhersteller schrieb mir, ich könne auch UTF-16 verwenden, wenn ich den fehlenden BOM $FFFE mitgeben würde. Ich werde die Funktion mit UTF-8 gleich einmal ausprobieren.

Viele Grüße
Ingo

ioster 31. Aug 2023 09:21

AW: WideStringlist in UTF-8 mit Delphi 2007 speichern
 
Zitat:

Zitat von Uwe Raabe (Beitrag 1526284)
In deinem Code speicherst du aber UTF-16 anstatt UTF-8. Ist das denn so gewollt?

Zitat:

Zitat von ioster (Beitrag 1526260)
ABER der Konverter streikt, da die neuen Textdateien in UTF-16-LE und nicht mehr in UTF-8 codiert werden.

Mit dieser kleinen Routine kannst du auch UTF-8 schreiben:
Delphi-Quellcode:
uses
  WideStrings, WideStrUtils;

procedure SaveToUTF8(const Filename: string; Content: TWideStrings);
var
  Stream : TFileStream;
  Zeile : UTF8String;
begin
  Zeile := UTF8Encode(Content.Text);
  Stream := TFileStream.Create(Filename, fmCreate);
  try
    Stream.WriteBuffer(sUTF8BOMString[1], Length(sUTF8BOMString));
    Stream.WriteBuffer(Zeile[1], Length(Zeile));
  finally
    Stream.Free;
  end;
end;

Die UTF8-Routine funktioniert. Vielen lieben Dank!!!

Was müsste ich denn jetzt ändern, um UTF16 ausgeben zu können. Die Konvertierung entfällt dann ja. Grundsätzlich dürfte sich das Verfahren ja nicht unterscheiden, oder?

Viele Grüße
Ingo

ioster 31. Aug 2023 11:13

AW: WideStringlist in UTF-8 mit Delphi 2007 speichern
 
Ich bin irgendwie zu blöd, um zu kapieren, wie man den BOM in eine UTF-16-Datei geschrieben bekommt.

Nun habe ich die UTF8-Lösung und habe auf dem CodeGear-Quellcode basierend das für den Fall der Fälle das für UTF-16 umsetzen wollen. Sobald ich den BOM wegschreibe, kommt in der Datei nichts Vernünftiges an.

Es dürfte doch jetzt eigentlich nur noch um eine Zeile gehen.

Delphi-Quellcode:
  BOM := WideChar($FFFE);
  Stream.WriteBuffer(BOM, SizeOf(BOM));
Muss ich bei WriteBuffer mit Length oder SizeOf arbeiten und wie muss ich den BOM bearbeiten?

himitsu 31. Aug 2023 14:19

AW: WideStringlist in UTF-8 mit Delphi 2007 speichern
 
Jupp, das #$FEFF ist das Unicode-BOM.

Es wird in der jeweiligen Codepage gespeichert. (wenn sie es darstellen kann)


Am Einfachsten als Char/WideChar an den Anfang des Textes.
BOM geht z.B. beim UTF-16-LE, UTF-16-BE und UTF-8. (ja, die 3 UTF8-Bytes sind jenes Char)

Delphi-Quellcode:
  Zeile := UTF8Encode(#$FEFF + Content.Text);
  Stream := TFileStream.Create(Filename, fmCreate);
  try
    //Stream.WriteBuffer(sUTF8BOMString[1], Length(sUTF8BOMString));
    Stream.WriteBuffer(Zeile[1], Length(Zeile)); // natürlich würde @Zeile[1] knallen, wenn der String leer ist
  finally
    Stream.Free;
  end;
Hach, wie schön doch ein Delphi-Referenz durchsuchenTFile.WriteAllText sein kann. :angle2:

Uwe Raabe 31. Aug 2023 14:42

AW: WideStringlist in UTF-8 mit Delphi 2007 speichern
 
Delphi-Quellcode:
procedure SaveToUTF16(const Filename: string; Content: TWideStrings);
var
  BOM: TBytes;
  Stream : TFileStream;
  Zeile : WideString;
begin
  Zeile := Content.Text;
  Stream := TFileStream.Create(Filename, fmCreate);
  try
    BOM := TBytes.Create($FF, $FE);
    Stream.WriteBuffer(BOM[0], Length(BOM));
    Stream.WriteBuffer(Zeile[1], Length(Zeile)*Sizeof(Zeile[1]));
  finally
    Stream.Free;
  end;
end;

himitsu 31. Aug 2023 15:35

AW: WideStringlist in UTF-8 mit Delphi 2007 speichern
 
Wie gesagt, Arrayzugriffe auf leere Strings/Arrays, knallen gern mal, daher
Delphi-Quellcode:
if Zeile <> '' then
  Stream.WriteBuffer(Zeile[1], Length(Zeile)*Sizeof(WideChar));

// oder

Stream.WriteBuffer(PWideChar(Zeile)^, Length(Zeile)*Sizeof(WideChar));
Wenn man das BOM vorher als CHAR in den String einfügt, ist es egal, da dann der String nie leer ist.




PS: die TWideStrings und TWideStringList der Unit Delphi-Referenz durchsuchenWideStrings speichern auch das BOM.
Weiß nicht ob und wie vor Delphi XE das gemacht wurde, aber diese Unit gab es auch schon in Delphi 7, wenn ich mich nicht total irre.

Delphi-Quellcode:
procedure TWideStrings.SaveToStream(Stream: TStream; Encoding: TEncoding);
var
  Buffer, Preamble: TBytes;
begin
  if Encoding = nil then
    Encoding := TEncoding.Unicode; // The default encoding is UTF-16

  Buffer := Encoding.GetBytes(string(GetTextStr));
  Preamble := Encoding.GetPreamble;

  if Length(Preamble) > 0 then
    Stream.WriteBuffer(Preamble[0], Length(Preamble));

  Stream.WriteBuffer(Buffer[0], Length(Buffer));
end;

ioster 31. Aug 2023 15:47

AW: WideStringlist in UTF-8 mit Delphi 2007 speichern
 
Zitat:

Zitat von himitsu (Beitrag 1526325)
Wie gesagt, Arrayzugriffe auf leere Strings/Arrays, knallen gern mal, daher
Delphi-Quellcode:
if Zeile <> '' then
  Stream.WriteBuffer(Zeile[1], Length(Zeile)*Sizeof(WideChar));

// oder

Stream.WriteBuffer(PWideChar(Zeile)^, Length(Zeile)*Sizeof(WideChar));
Wenn man das BOM vorher als CHAR in den String einfügt, ist es egal, da dann der String nie leer ist.




PS: die TWideStrings und TWideStringList der Unit [OH]WideStrings[OH] speichern auch das BOM.
Weiß nicht ob und wie vor Delphi XE das gemacht wurde, aber diese Unit gab es auch schon in Delphi 7, wenn ich mich nicht total irre.

Delphi-Quellcode:
procedure TWideStrings.SaveToStream(Stream: TStream; Encoding: TEncoding);
var
  Buffer, Preamble: TBytes;
begin
  if Encoding = nil then
    Encoding := TEncoding.Unicode; // The default encoding is UTF-16

  Buffer := Encoding.GetBytes(string(GetTextStr));
  Preamble := Encoding.GetPreamble;

  if Length(Preamble) > 0 then
    Stream.WriteBuffer(Preamble[0], Length(Preamble));

  Stream.WriteBuffer(Buffer[0], Length(Buffer));
end;

In Delphi 2007 ist das offenbar nicht der Fall. Die originäre Routine von CodeGear hatte ich eingangs als Delphi-Quellcode eingefügt. Der fehlende BOM verursachte für mich erst das Problem, weil der Konverter ohne dem die Dateiinhalte nicht lesen konnte.

Dein Quellcode mit der UTF8-Umwandlung habe ich nicht verstanden. Als Ergebnis kam bei mir eine ANSI-Datei heraus, deren Zeichen mit einem Leerzeichen voneinander getrennt waren.

Die Lösung ist jetzt aber dank Uwe Raabe da. Der Ansatz funktioniert, wobei ich immer noch nicht durchblicke, wann mit SizeOf, Length etc. gearbeitet werden muss. Ich hatte mir etliche Beispiele angeschaut, die sich aber meistens auf neuere Delphi-Versionen bezogen und somit nicht 1:1 umzusetzen waren.

Viele Grüße
Ingo

ioster 31. Aug 2023 15:49

AW: WideStringlist in UTF-8 mit Delphi 2007 speichern
 
Zitat:

Zitat von Uwe Raabe (Beitrag 1526318)
Delphi-Quellcode:
procedure SaveToUTF16(const Filename: string; Content: TWideStrings);
var
  BOM: TBytes;
  Stream : TFileStream;
  Zeile : WideString;
begin
  Zeile := Content.Text;
  Stream := TFileStream.Create(Filename, fmCreate);
  try
    BOM := TBytes.Create($FF, $FE);
    Stream.WriteBuffer(BOM[0], Length(BOM));
    Stream.WriteBuffer(Zeile[1], Length(Zeile)*Sizeof(Zeile[1]));
  finally
    Stream.Free;
  end;
end;

DANKE!!!! Das hat geholfen, wobei ich das inhaltlich immer noch nicht verstehe. Doch ich kann das Programm nun wieder in Kombination mit dem Konverter einsetzen ohne manuell per Editor den Dateiinhalt mit einer anderen Codierung abspeichern zu müssen.

Viele Grüße
Ingo

himitsu 31. Aug 2023 16:22

AW: WideStringlist in UTF-8 mit Delphi 2007 speichern
 
der eizige Unterschied ist
* man kann das codierte BOM und anschließend den codierten Text in den Stream/Datei schreiben
* man kann aber auch das uncodierte BOM in den Text einfügen und das dann zusammen codiert in den Stream/Datei schreiben


#$FFFF ist ungültig/verboten (laut Unicode-Standard)
#$FFFE ist ungültig/verboten, aber wird als Char für den ByteOrderMark verwendet
#$FEFF ist ungültig/verboten, wegen Konflikt mit dem ByteOrderMark
und der Bereich #$Fxxxxxxx ist sowieso nicht existent (somit kein Konflikt möglich)

Folgendes sind BOM, welche alle dem Char #$FFFF entsprechen, jeweils in ihrer Codepage dargestellt.
EF BB BF = UTF-8
FE FF = UTF-16 (sowie auch UCS2), jeweils BigEndian und LittleEndian (kleinstes oder größtes Byte der Chars zuerst)
FF FE
00 00 FE FF = UTF-32, jeweils BigEndian und LittleEndian
FF FE 00 00


https://www.compart.com/de/unicode/U+FEFF
https://de.wikipedia.org/wiki/Byte_Order_Mark

Achtung $FF $FE ist $FEFF und nicht $FFFE


Windows ist LittleEndian (nicht so wie Apple früher mal BigEndian)
und in Dateien wird oft auch LittleEdnian gespeichert (also praktischer Weise sind somit Datei und RAM gleich)
-> zuerst das kleinste Byte
-> beim Dezimal und HEX aber die Ziffern der einzelnen Bytes wiederum BigEndian :lol:

Größere mehrstellige "Zahlen" sind aber BigEndian, im Text/Quellcode
-> zuerst die größte Ziffer

Im HTML/CSS sind Farbwerte aber andersrum, also LittleEndian, gegen über TColor -> #RRGGBB vs. $BBGGRR :wall:

mytbo 31. Aug 2023 16:28

AW: WideStringlist in UTF-8 mit Delphi 2007 speichern
 
Wenn umfangreicher mit UTF-8 gearbeitet wird, kannst du ab Delphi 7 mORMot verwenden. Ist der Eingang ein WideString, dann mit WideStringToUtf8 konvertieren:
Delphi-Quellcode:
type
  mormot.core.data,
  mormot.core.unicode;
 
var
  line: WideString;
  list: TRawUtf8List;
begin
  list := TRawUtf8List.Create;
  try
    line := '...';
    list.Add(WideStringToUtf8(line));
    list.SaveToFile('test.txt'); // write all lines into a new UTF-8 file
  finally
    list.Free;
  end;
Ansonsten den String-Typ RawUtf8 verwenden. Mit den Funktionen StringToUtf8/Utf8ToString diesen Typ in allen Delphi Versionen konvertieren.

Bis bald...
Thomas

himitsu 31. Aug 2023 16:43

AW: WideStringlist in UTF-8 mit Delphi 2007 speichern
 
Eine Delphi-Vesion weiter (2009) war Unicode/UTF-8 dann auch nativ besser nutzbar. :duck:


Alle Zeitangaben in WEZ +1. Es ist jetzt 23:41 Uhr.

Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz