UTF-8, UTF8Length, TListbox.Items.LoadFromFile

Rund um die LCL und andere Komponenten
Antworten
HHick123
Beiträge: 21
Registriert: Mo 10. Nov 2014, 00:28

UTF-8, UTF8Length, TListbox.Items.LoadFromFile

Beitrag von HHick123 »

Hallo Leute, ich bin auf folgendes Problem gestossen,
und nehme an, es handelt sich um einen Bug in TListbox.Items.LoadfromFile bzw. in UTF8Length (aus lazutf8).

Ist es ein Bug, oder bin ich nur zu blöd dafür...?

Windows 7,
FPC-Version 3.1.1,
64bit

Ich habe eine csv-Datei mit Excel erstellt, welche nur in der obersten linken Zelle das Wort "Abfüllstation" enthält.
In Notepad++ sehe ich, dass sie die Codierung "UTF-8" (also quasi mit BOM) hat.
Hexadezimal sieht das komplette File so aus:
00000000 ef bb bf 41 62 66 c3 bc 6c 6c 73 74 61 74 69 6f
00000010 6e 0d 0a

Ok, so nun eine Listbox aufs Formular und einen Timer und folgende Ereignisbehandlung:

procedure TForm1.Timer1Timer(Sender: TObject);
var
s, s1, s2:string;
begin
Timer1.enabled:=false;
Listbox1.Items.LoadFromFile('test.csv');
Listbox1.Items.Add(UTF8Trim(Listbox1.Items[0]));
s1:=Listbox1.Items[0];
s2:=Listbox1.Items[1];
s:='Abfüllstation';
Form1.Listbox1.Items.Add('UTFLength(s1) = ' + inttostr(UTF8Length(s1)));
Form1.Listbox1.Items.Add('UTFLength(s2) = ' + inttostr(UTF8Length(s2)));
if s1=s then Listbox1.Items.Add('s1 = s') else Listbox1.Items.Add('s1 <> s');
if s2=s then Listbox1.Items.Add('s2 = s') else Listbox1.Items.Add('s2 <> s');
//s1 beginnt anscheinend mit einem unsichtbaren Zeichen (3 Bytes UTF-8-Byte-Order-Marking) 239 187 191
end;


Der Output in der Listbox ist:

Abfüllstation
Abfüllstation
UTFLength(s1) = 14
UTFLength(s2) = 13
s1 <> s
s2 = s


Debuggen ergibt, dass anscheinend s1 mit der unsichtbaren Bytefolge 239 187 191 (BOM) beginnt.
Allerdings muss man sagen, dass das ein gemeines Problem ist, weil man das unsichtbare Zeichen ja auch nicht so ohne weiteres im Debugger sieht. Zumindest mir war am Anfang nicht klar, warum der Vergleich s1=s false ergibt.

Quintessenz:

- Die Listbox läd zumindest den ersten UTF-8-String mit BOM herein.
- Die Listbox zeigt diesen String und den mit Add hinzugefügten String gleich an.
- UTF8Length liefert für den ersten String aber 14 anstatt 13 (d.h. das BOM zählt mit; Bug??)
- UTF8Trim behebt das Problem
- Das BOM bewirkt, dass der Gleichheitsoperator fehlschlägt
- UTF8CompareStr ist auch false (wozu ist das eigentlich)

Sollte nicht LoadFromFile der Listbox das BOM des strings entfernen?
Mein Workaround ist aktuell, nach LoadFromFile in einer Schleife für jedes TListbox.Item UTF8Trim aufzurufen, aber das kann ja auch nicht im Sinne des Erfinders sein..... Andere Leute wollen vielleicht Strings einlesen, die auf Leerzeichen enden.

LG Helmut

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

Re: UTF-8, UTF8Length, TListbox.Items.LoadFromFile

Beitrag von theo »

TStrings interessiert sich afaik derzeit nicht für "Unicode Kram". Ich kann mich aber irren, weil alles im Wandel ist.
Das ist nicht direkt ein Bug, sondern einfach nicht implementiert.

Wenn du alle möglichen Quellen laden willst, kannst du für den Moment meinen TCharEncStream nehmen.
http://wiki.freepascal.org/UTF8_Tools#Using_streams

HHick123
Beiträge: 21
Registriert: Mo 10. Nov 2014, 00:28

Re: UTF-8, UTF8Length, TListbox.Items.LoadFromFile

Beitrag von HHick123 »

Danke, werd' ich mir ansehen.

Ich kämpfe derzeit mit UTF8 auf verschiedenen Fronten. Zum Verzweifeln ist das...
Das Drama ist allerdings, das die Listbox anscheinend, obwohl das Laden von UTF-8 nicht richtig funktioniert/ nicht implementiert ist, jedoch UTF8-Strings benötigt, damit die Umlaute richtig angezeigt werden....

Ein anderes Problem, an dem ich gerade verzweifle ist, an einen UTF8-String ein Zeichen '\' anzuhängen.
Man könnte naiverweise meinen, das sollte mit dem + Operator gehen.
Tja sieht nicht so aus, zumindest schaff ich's aktuell nicht. Dabei werden anscheinend (polnische) Zeichen im UTF8-String durch "ähnliche" Zeichen ersetzt.

Leute, das ist alles noch viel zu kompliziert....

LG Helmut
Zuletzt geändert von HHick123 am Do 28. Jan 2016, 22:58, insgesamt 1-mal geändert.

wp_xyz
Beiträge: 4889
Registriert: Fr 8. Apr 2011, 09:01

Re: UTF-8, UTF8Length, TListbox.Items.LoadFromFile

Beitrag von wp_xyz »

Nein, ist es nicht, ich hab's schließlich auch irgendwann mal begriffen...

Zunächst musst du wissen, dass du, wenn du mit Lazarus arbeitet, eigentlich mit zwei Produkten zu tun hast, FPC und Lazarus, die leider bis vor kurzem bezüglich der Strings eigene Wege gingen. Die Routinen, die zum FPC gehören, - und dazu gehören die Dateizugriffsoperationen! - verwenden ANSI-Strings (1-Byte Zeichen jeweils auf einer Code-Page), und wenn du mit Lazarus arbeitest - das sind all die Elemente der visuellen Programmiereung (z.B. die Listbox, von der du sprichst) -, dann sind die Strings UTF-8-kodiert, also Strings mit mindestens 1 Byte pro Zeichen, wobei die ersten 128 Zeichen mit unserer ANSI-Code-Page übereinstimmen, aber im Prinzip alle Zeichen ansprechbar sind, ohne etwas umstellen zu müssen.

Für den Übergang von der FPC- in die Lazarus-String-Welt gibt es Konvertierungsfunktionen, z.B, AnsiToUTF8, oder UTF8ToAnsi für die andere Richtung. Etwas allgemeiner sind SysToUTF8/UTF8ToSys, was bei Windows AnsiToUTF8/UTF8ToAnsi entspricht, aber bei Unix nichts macht, weil dort die System-Kodierung ohnehin UTF8 ist. Weitere Konvertierungsfunktionen zum Beispiel für andere CodePages findest du in den Units LConvEncoding und LazUTF8. Dort gibt es auch eine, die den BOM richtig behandelt.

Falls dir das alles immer noch zu komplizert ist, solltest du auf FPC 3.0 wechseln, der auch mit UTF8 arbeitet, so dass all (besser: die meisten) Konvertierungen nicht mehr nötig sind. Warte vielleicht noch ein paar Tagen oder Wochen, dann kommt Lazarus 1.6 raus, hier wird FPC 3.0 standardmäßig installiert, oder nimm schon mal den RC2.

HHick123
Beiträge: 21
Registriert: Mo 10. Nov 2014, 00:28

Re: UTF-8, UTF8Length, TListbox.Items.LoadFromFile

Beitrag von HHick123 »

Nun ja, mag sein. Ich hoffe, dass es im Lauf der Zeit da noch Fortschritte gibt.
Als jahrzehntelanger Fan setze ich meine zukünfitigen Hoffnungen natürlich aktuell stark in FreePascal bzw. Lazarus.

Falls es noch jemanden interessiert, ich hab' mir in meiner Not gerade einen Workaround gebastelt, um je zwei UTF-8-Strings mit polnischen Sonderzeichen (bislang hatte ich das Problem nie, weil ich die nie brauchte) zu verbinden. Wenn ich die Strings auf widestring caste funktioniert der + Operator.

[Edit: Text gekürzt]

Code: Alles auswählen

 
 [Edit: Hier stand eine Funktion UTF8Concat, die ich als Workaround verwendet hatte, um zwei strings mit polnischen Sonderzeichen zusammenzusetzen. Das kann aber nicht die Methode der Wahl sein, daher lösche ich den Blödsinn lieber wieder weg...]
 



LG Helmut
Zuletzt geändert von HHick123 am Fr 29. Jan 2016, 00:32, insgesamt 5-mal geändert.

wp_xyz
Beiträge: 4889
Registriert: Fr 8. Apr 2011, 09:01

Re: UTF-8, UTF8Length, TListbox.Items.LoadFromFile

Beitrag von wp_xyz »

Ich versteh nicht, was du da machst. Man kann ohne Probleme UTF8-Strings mit "+" zusammenhängen. Sind deine polnischen Strings wirklich UTF8? Ich nehme an, du hast die Zeichen auf der polnischen Code-Page. Dann solltest du diese Art von Ansi-Strings mit ISO_8859_2ToUTF8 (aus lclconvencoding) nach UTF8 konvertieren (ich nehme an, die polnische Code-Page ist ISO8859_2, "Eastern Europe").

Hier ein Beispiel dafür, dass das "+" mit griechischen UTF8-Zeichen funktioniert (Nimm zum Testen ein Formular mit 3 Labels):

Code: Alles auswählen

procedure TForm1.FormCreate(Sender: TObject);
begin
  Label1.Caption := 'αβγδεζη';
  Label2.Caption := 'ΑΒΓΔΕΖΗΘΙΚΜ';
  Label3.Caption := Label1.Caption + Label2.Caption;
end;

Michl
Beiträge: 2505
Registriert: Di 19. Jun 2012, 12:54

Re: UTF-8, UTF8Length, TListbox.Items.LoadFromFile

Beitrag von Michl »

HHick123 hat geschrieben:Ist es ein Bug...?
Interessant! Ja, würde ich so sagen.

Ich habe es eben mal nachgebaut, das BOM wird als Character interpretiert, sollte aber IMHO nicht als solches gewertet werden.

Habe es eben noch mit einem Lazarus 1.4 probiert, da wird das BOM ebenfalls als Character geladen. Anbei der Test, falls es noch jemanden interessiert.

K.A., ob das BOM als Character gewertet werden sollte oder nicht, für mich sieht das nach einem Bug aus. MMn sollte es nicht mit geladen werden. Ich würde da mal in der Mailinglist oder im Bugtracker nachfragen.

PS: Für die Funktion UTF8Concat sehe ich keinen Anlass, ansonsten funktionieren die UTF8-Tools recht problemlos, wie wp schon schrieb. Kannst du mal ein minimiertes Beispiel anhängen, wo du diese benötigst?
Dateianhänge
TestAbfuellstation.zip
(2.07 KiB) 47-mal heruntergeladen
Zuletzt geändert von Michl am Do 28. Jan 2016, 23:59, insgesamt 1-mal geändert.

Code: Alles auswählen

type
  TLiveSelection = (lsMoney, lsChilds, lsTime);
  TLive = Array[0..1] of TLiveSelection; 

wp_xyz
Beiträge: 4889
Registriert: Fr 8. Apr 2011, 09:01

Re: UTF-8, UTF8Length, TListbox.Items.LoadFromFile

Beitrag von wp_xyz »

Einen Bug-Report gibt es schon: http://bugs.freepascal.org/view.php?id=27088

Außerdem sind nach "ConvertEncoding(string, encodingUTF8BOM, encodingUTF8)" die Strings wieder gleich.

Code: Alles auswählen

procedure TForm1.FormCreate(Sender: TObject);
begin
  Listbox1.Items.LoadFromFile('test.txt');
  Listbox1.Items.Text := ConvertEncoding(Listbox1.Items.Text, encodingUTF8BOM, encodingUTF8);   // <--- neu
  Listbox1.Items.Add(UTF8Trim(Listbox1.Items[0]));
  ListBox1.Items.Add(PStringToHex(Pointer(Listbox1.Items[0]), Length(Listbox1.Items[0])));
  ListBox1.Items.Add(PStringToHex(Pointer(Listbox1.Items[1]), Length(Listbox1.Items[1])));
  ListBox1.Items.Add('Length[0]: ' + IntToStr(UTF8Length(ListBox1.Items[0])));
  ListBox1.Items.Add('Length[1]: ' + IntToStr(UTF8Length(ListBox1.Items[1])));
end;

Michl
Beiträge: 2505
Registriert: Di 19. Jun 2012, 12:54

Re: UTF-8, UTF8Length, TListbox.Items.LoadFromFile

Beitrag von Michl »

@wp: Den Bugreport kannte ich nicht. Evtl. sollte dieser forciert werden. Scheinbar ist das ja auch recht problemlos möglich, wie du eben bewiesen hast.
Zuletzt geändert von Michl am Fr 29. Jan 2016, 00:57, insgesamt 2-mal geändert.

Code: Alles auswählen

type
  TLiveSelection = (lsMoney, lsChilds, lsTime);
  TLive = Array[0..1] of TLiveSelection; 

HHick123
Beiträge: 21
Registriert: Mo 10. Nov 2014, 00:28

Re: UTF-8, UTF8Length, TListbox.Items.LoadFromFile

Beitrag von HHick123 »

Ok. Das Beispiel mit den drei Labels oben funktioniert einwandfrei.
Auch für die polnischen Sonderzeichen.

Ich hab' meinen Workaround "UTF8Concat" oben rausgelöscht: Ist ziemlich sicher Blödsinn!
Nicht, dass das noch einer nachmacht....

Dabei handelt es sich um ein anderes Projekt. Wahrscheinlich liegt's daran, dass dort meine Strings nicht korrekt UTF-8 sind, oder so.
Ich mache dafür eventuell dann einen anderen Thread auf, damit sich die Themen hier nicht vermischen...

LG Helmut

Antworten