Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Cross-Platform-Entwicklung (https://www.delphipraxis.net/91-cross-platform-entwicklung/)
-   -   Delphi und Linux - Encoding-Salat? (https://www.delphipraxis.net/201626-delphi-und-linux-encoding-salat.html)

knaeuel 8. Aug 2019 11:33

Delphi und Linux - Encoding-Salat?
 
Liste der Anhänge anzeigen (Anzahl: 1)
Ich bastele gerade an einer XML-Schnittstelle, welche unter Linux und Windows funktionieren soll. Die XML-Dateien kommen ISO-8859-1 kodiert hier an. Das sollte "ANSI - Lateinisch I" entsprechen, wenn ich das richtig sehe.

Unter Windows gibt es keinerlei Probleme mit den Dateien. Einfach in den Parser werfen und los gehts. Linux ist da schon etwas zickiger. Sobald eine Datei geladen wird, die einen Umlaut (ä,ö,ü) enthält, kommt die Fehlermeldung

"No mapping for the Unicode character exists in the target multi-byte code page"

Dasselbe würde sicherlich auch noch bei dem einen oder anderen Sonderzeichen passieren.

Ok, kein Problem, dachte ich mir. Konvertieren wir halt die Dateien zu Unicode, dann sollte es laufen. Zur Konvertierung gibt es ein prima Beispiel von Delphi - rausgesucht und in mein Programm eingebaut.

Unter Windows und unter Linux kann ich jetzt das Encoding der XML-Dateien anzeigen lassen und wandeln und wieder anzeigen lassen. Theoretisch... Folgendermaßen sind die Ergebnisse, wenn ich die Kodierung von einigen Dateien mit Windows und mit Linux bestimmen lasse (Spalten "Abfrageergebnisse") und wenn ich versuche, die XML-Datei mit dem Parser zu öffnen:
Anhang 51535

Für mich ist das Encoding-Thema ziemlich neu. Ist es erwartungsgemäß, dass Windows die Ascii-Datei für Ansi erklärt? Und wie siehts mit Linux aus? Liegt das mit den ganzen angeblichen UTF-8-Dateien am Dateisystem? Oder macht mein Code nicht was er soll? Oder hat Ddelphi da Schwächen?

Quellcode: Umwandlung
Delphi-Quellcode:
function TDM_XMLSchnittstelle.ConvertFile(filename:string;destination:TEncoding;filename_option:integer):string;
var LBuffer: TBytes;
    LByteOrderMark: TBytes;
    LOffset: Integer;
    LEncoding, DestEncoding: TEncoding;
    LFileStream: TFileStream;
    out_filename:string;
begin
  LEncoding:= nil;
  DestEncoding := destination;
  LFileStream := TFileStream.Create(filename, fmOpenRead);
  try
    //Datei einlesen und im LBuffer ablegen
    SetLength(LBuffer, LFileStream.Size);
    LFileStream.ReadBuffer(Pointer(LBuffer)^, Length(LBuffer));

    //aktuelle Kodierung einlesen
    LOffset := TEncoding.GetBufferEncoding(LBuffer, LEncoding);
    //Konvertierung zur gewünschten Ziel-Kodierung durchführen
    LBuffer := LEncoding.Convert(LEncoding, DestEncoding, LBuffer, LOffset, Length(LBuffer) - LOffset);
  finally
    LFileStream.Free;
  end;

  if filename_option=0 then
    out_filename:=filename
  else if filename_option=1 then
    out_filename:=filename+'.converted'
  else if filename_option=2 then
    out_filename:=filename+'.'+DestEncoding.EncodingName;

  LFileStream := TFileStream.Create(out_filename, fmCreate);
  try
    //zuerst Encoding-Byte-Order-Mark in die Datei schreiben
    LByteOrderMark := DestEncoding.GetPreamble;
    LFileStream.Write(LByteOrderMark[0], Length(LByteOrderMark));
    //danach den Buffer wegschreiben
    LFileStream.Write(LBuffer[0], Length(LBuffer));
  finally
    LFileStream.Free;
  end;
end;
Quellcode Abfrage der Kodierung:
Delphi-Quellcode:
function TDM_XMLSchnittstelle.GetCurrentEncoding(filename:string):TEncoding;
var LBuffer: TBytes;
    LOffset: Integer;
    LEncoding: TEncoding;
    LFileStream: TFileStream;
begin
  LFileStream := TFileStream.Create(filename, fmOpenRead);
  try
    //Datei einlesen und im LBuffer ablegen
    SetLength(LBuffer, LFileStream.Size);
    LFileStream.ReadBuffer(Pointer(LBuffer)^, Length(LBuffer));
    //aktuelle Kodierung einlesen
    LEncoding:= nil; //<-- wichtig, weil die nächste Procedure sonst Käse zurückliefert!
    LOffset := TEncoding.GetBufferEncoding(LBuffer, LEncoding);
  finally
    LFileStream.Free;
  end;
  result:=LEncoding;
end;
Als Quelldatei diente ursprünglich eine ANSI-Datei, die ich mit dieser Konvertierungs-Function umgewandelt habe (unter Windows).

Warum wird ASCII unter Windows nicht als ASCII erkannt?
Warum erkennt Linux gleich mehrere Kodierungen als UTF-8?

Schokohase 8. Aug 2019 11:49

AW: Delphi und Linux - Encoding-Salat?
 
Wenn du die XML-Datei hier noch gepostet hättest, dann wäre es etwas einfacher.
(Muss ja nicht das Original sein, sondern eine beispielhafte die das gleiche Verhalten zeigt)

knaeuel 8. Aug 2019 12:07

AW: Delphi und Linux - Encoding-Salat?
 
Liste der Anhänge anzeigen (Anzahl: 1)
oh ja, kein Problem, in der zip-Datei befinden sich 6 varianten derselben Datei in unterschiedlichen Kodierungen, alle mit Windows mit dem obigen Code umgewandelt. Das Original liegt ebenfalls dabei.

Anhang 51536

Schokohase 8. Aug 2019 12:13

AW: Delphi und Linux - Encoding-Salat?
 
Ja, das ist kein Wunder, denn du schreibst in der XML-Datei
XML-Code:
<?xml version="1.0" encoding="ISO-8859-15"?>
...
was für ein Encoding du verwendest ... aber du hälst dich daran einfach nicht, sondern nimmst jedes Encoding, was dir in den Sinn kommt.

Also, wenn du ein XML-Datei in UTF-16 kodiert speicherst, dann muss die Datei anfangen mit
XML-Code:
<?xml version="1.0" encoding="UTF-16"?>
...
und wenn du die in UTF-8 kodiert speicherst, dann muss die Datei anfangen mit
XML-Code:
<?xml version="1.0" encoding="UTF-8"?>
...
oder
XML-Code:
<?xml version="1.0"?>
...

knaeuel 8. Aug 2019 12:38

AW: Delphi und Linux - Encoding-Salat?
 
nein, das trifft das Problem nicht. Selbstverständlich korrigiere ich die erste Zeile bei Bedarf, bevor ich die Daten dem XML-Parser vorwerfe.

Was die Kodierung angeht, sind wir einfach nur bei Textdateien. Die Konvertierungsversuche bzw. das Einlesen der Kodierung ist völlig unabhängig vom XML-Code. Da wird nichts ausgewertet.

Der Fehler "No mapping for the Unicode character exists in the target multi-byte code page" tritt auf, sobald die Datei in eine Stringlist geladen wird. z.B. so:

Delphi-Quellcode:
var dat:TStrings;
...
dat:=TStringlist.Create;
dat.LoadFromFile(dateiname); //<-- erzeugt Fehler unter Linux

jobo 8. Aug 2019 12:46

AW: Delphi und Linux - Encoding-Salat?
 
Die Varianten sind ja wohl im Versuch entstanden, es Linux recht zu machen, wenn ich das richtig verstanden habe.

Aber letztlich ist es wie es ist, einer der Vorteile von XML ist, dass die Datei selbst klar definiert, wie die Kodierung ist. Das muss man dann "einfach" einhalten.
Linux kann das mit Sicherheit. Im eigenen Programm muss man natürlich selber dafür sorgen.

@roter Kasten.
Auch wenn man die Codierung ignoriert, muss ja nichts gutes dabei rauskommen

knaeuel 8. Aug 2019 12:51

AW: Delphi und Linux - Encoding-Salat?
 
Liste der Anhänge anzeigen (Anzahl: 1)
Danke für die Antworten bis hierhin, aber bitte geht doch auf die Fragen ein:

Warum wird ASCII unter Windows nicht als ASCII erkannt?
Warum erkennt Linux gleich mehrere Kodierungen als UTF-8?

Siehe Tabelle:
Anhang 51537

Vergesst dabei bitte den XML-Part und stellt euch vor, es ginge einfach nur um Textdateien.

Klaus01 8. Aug 2019 12:58

AW: Delphi und Linux - Encoding-Salat?
 
XML Reader bekommen die encoding Info aus dem XML Header.
Gewöhnliche Textdateien haben ein Byte-Order-Mark (BOM)

Warum wird die xml Datei in eine StringList eingelesen?

Grüße
Klaus

jobo 8. Aug 2019 13:03

AW: Delphi und Linux - Encoding-Salat?
 
Um die Fragen zu beantworten, müsste man die Implementierung von getbufferencoding kennen und sie mit Deinen Textdateibeispielen debuggen?
Ist diese Frage, also eigentlich "getBufferEndcoding" überhaupt deterministisch? Oder ist es eine Frage davon, welches Stück Text gerade im Buffer steckt?
Wenn mehrere Encodings als Antwort möglich sind, welche gibt getBufferEncoding zurück?
Kann der Text auch kaputt encoded sein oder schließt Du das aus?

Schokohase 8. Aug 2019 14:03

AW: Delphi und Linux - Encoding-Salat?
 
Delphi-Referenz durchsuchenTEncoding.GetBufferEncoding

Und die Doku sagt, nur wenn es ein BOM gibt, dann gibt es ein passendes Encoding zurück, ansonsten gibt es Default zurück.

Und bei ASCII gibt es kein BOM also ...

Achim Kalwa 8. Aug 2019 14:14

AW: Delphi und Linux - Encoding-Salat?
 
Zitat:

Zitat von knaeuel (Beitrag 1440230)
Warum wird ASCII unter Windows nicht als ASCII erkannt?
Warum erkennt Linux gleich mehrere Kodierungen als UTF-8?

ASCII umfasst die Zeichen mit den Codes von 0 bis 127 und ist damit eine Untermenge von ANSI. Auch die ersten 128 Zeichen bei UTF-8 sind identisch mit ASCII.

knaeuel 8. Aug 2019 14:33

AW: Delphi und Linux - Encoding-Salat?
 
ah, danke, manchmal sieht man den Wald vor Bäumen nicht mehr. Delphi schaut nur aufs BOM => unter Linux wird alles ohne BOM als UTF-8 deklariert.

Dann werde ich zumindest die Default-Entscheidung für Linux anpassen.

ASCII/UTF7/ANSI-Dateien werden bei Linux korrekt als nicht-UTF8 erkannt und danach trotzdem als UTF8 deklariert. Dann nehme ich da lieber auch ANSI als default. Dann passt es wenigstens oftmals und nicht mehr nie.

Abschließend nochmal die beantworteten Fragen:

Zitat:

Zitat von knaeuel (Beitrag 1440210)
Ist es erwartungsgemäß, dass Windows die Ascii-Datei für Ansi erklärt? Und wie siehts mit Linux aus?

Ja, das ist erwartungsgemäß, denn Delphi unterscheidet einfach nicht weiter.

Zitat:

Zitat von knaeuel (Beitrag 1440210)
Liegt das mit den ganzen angeblichen UTF-8-Dateien bei Linux am Dateisystem? Oder macht mein Code nicht was er soll? Oder hat Ddelphi da Schwächen?

Ja, das würde ich als kleine Schwäche bezeichnen. Etwas besser ist die Programmierung unter Windows und es leuchtet mir auch nicht ein, wieso das nicht für Win und Linux gleich läuft. Bei mir läuft es jetzt gleich. (Default-Rückgabe bei Dateien ohne BOM)

Zitat:

Zitat von knaeuel (Beitrag 1440210)
Warum wird ASCII unter Windows nicht als ASCII erkannt?

Bei Dateien ohne BOM unterscheidet Delphi einfach nicht weiter und gibt den default-Wert (Windows: ANSI) zurück.

Zitat:

Zitat von knaeuel (Beitrag 1440210)
Warum erkennt Linux gleich mehrere Kodierungen als UTF-8?

Bei Dateien ohne BOM unterscheidet Delphi einfach nicht weiter und gibt den default-Wert (Linux: UTF8; bei mir jetzt geändert auf ANSI) zurück.

Zitat:

Zitat von Achim Kalwa (Beitrag 1440248)
Zitat:

Zitat von knaeuel (Beitrag 1440230)
Warum wird ASCII unter Windows nicht als ASCII erkannt?
Warum erkennt Linux gleich mehrere Kodierungen als UTF-8?

ASCII umfasst die Zeichen mit den Codes von 0 bis 127 und ist damit eine Untermenge von ANSI. Auch die ersten 128 Zeichen bei UTF-8 sind identisch mit ASCII.

Danke für die Hilfe!

knaeuel 9. Aug 2019 13:06

AW: Delphi und Linux - Encoding-Salat?
 
kleiner Nachtrag:

tatsächlich würde ich inzwischen von einem Bug in der System.SysUtils.pas sprechen - nur für Linux.

ANSI-Dateien werden fälschlicherweise als UTF-8 deklariert und können dann nicht z.B. in eine Stringlist geladen werden (Fehlermeldung "No mapping for the Unicode character exists in the target multi-byte code page"). Der Default-Wert beim Feststellen der Kodierung sollte nicht UTF-8 sein, wenn UTF-8 vorher explizit ausgeschlossen wurde.

Macht man sich die Mühe und surft mit dem Debugger durch die Bibliotheken, wenn man unter Linux eine ANSI-Datei in eine Stringlist laden will (was überigens auch beim Öffnen einer Datei mit der XMLDocument-Komponente passiert), dann kann man im richtigen Moment eingreifen und die Kodierung von UTF-8 auf ANSI korrigieren. Dann wird die Datei fehlerfrei geladen. Macht man das während des Ladens der Datei in die XMLDoc-Komponente, dann kann die XML-Datei anschließend fehlerfrei ausgewertet und angezeigt werden - inklusive Sonderzeichen!

Der Fehler hat seinen Ursprung (wie gesagt) beim Feststellen der Kodierung, für Stringlisten in System.Classes.pas in Zeile 6894. Dort wird TEncoding.GetBufferEncoding (System.SysUtils.pas Zeile 32708) aufgerufen, was dann unter Linux den falschen default-Wert "UTF-8" zurückliefert.

Liefert diese function das korrekte Ergebnis "ANSI" (durch manuellen Eingriff) zurück, läuft anschließend alles fehlerfrei.
Liefert sie für eine ANSI-Datei den Standardwert UTF-8 zurück, klappt die Umwandlung der ANSI-Sonderzeichen nicht, weil die Kodierung nicht zu UTF-8 passt.

Aktuell habe ich nur eine geerbte Klasse TMyEncoding = class(TEncoding) in der ich dann ein paar Dinge überschrieben habe. Dadurch kann ich wenigstens schonmal selber die richtigen Codierungen angeben.
Allerdings wird aus den Systembibliotheken natürlich nach wie vor auf die Hauptklasse TEncoding zugegriffen.

Gibt es eine Möglichkeit, die Original-Function irgendwie zu überschreiben?

Es geht um die "class function TEncoding.GetDefault: TEncoding;" bzw. "class property Default: TEncoding read GetDefault;" aus der Klasse TEncoding, System.SysUtils.pas

Schokohase 9. Aug 2019 14:16

AW: Delphi und Linux - Encoding-Salat?
 
Deine Schlußfolgerungen sind leider falsch.

Unter Linux ist das Default Encoding UTF-8 weil das Betriebs-System so eingestellt ist.

Das ist also kein Fehler von Delphi (insofern es nicht hart kodiert ist sondern den Wert vom OS ausliest).

Ich weiß, du glaubst mir nicht, also hier der Beweis:

Führe auf der Konsole folgendes aus:
Code:
locales
Was steht da?

Bei mir kommt z.B.
Code:
LANG=de_DE.UTF-8
Würde ich mich da wundern, wenn als Default Encoding auf diesem System UTF-8 kommt? NEIN!

knaeuel 9. Aug 2019 14:34

AW: Delphi und Linux - Encoding-Salat?
 
naja, Default meint hier nicht (oder sagen wir "sollte nicht meinen") das Betriebssystem-Default, sondern einfach einen Notfallwert, wenn kein BOM vorhanden ist. (BOMs sind bei UTF8 ja Standard)

ich schätze, mit genau der Überlegung kam es zu der Entscheidung, UTF-8 als Fallback zu setzen. Aber sinnvoll ist es eher nicht, würde ich sagen.

1. es ist hart kodiert. (siehe class function TEncoding.GetDefault: TEncoding; in System.SysUtils.pas)

2. die Procedure TEncoding.GetBufferEncoding (System.SysUtils.pas Zeile 32708) prüft zunächst, ob die Kodierung UTF-8, Unicode LE oder Unicode BE ist. Trifft alles NICHT zu, wird der FEST KODIERTE Wert UTF-8 zurückgegeben, OBWOHL direkt vorher festgestellt wurde, dass es UTF-8 NICHT sein kann.

3. Das führt dazu, dass unter Linux keine ANSI-Dateien in Strings geladen werden können. Sobald du das versuchst, versucht Delphi einen UTF8-String einzulesen, obwohl ein ANSI-String kommt. Das führt bei Umlauten und vielen anderen Sonderzeichen zum Abbruch mit der Meldung "No mapping for the Unicode character exists in the target multi-byte code page". Kein Wunder -> es ist ja auch kein Unicode-Character sondern ein ANSI-Character, der zu verarbeiten wäre.

Also für mich ist das ein Bug. Ich habe inzwischen einen funktionierenden Workaround und kann jetzt einfach meine ANSI-kodierten XML-Dateien unter Linux öffnen. Fehlerfrei. Alle Sonderzeichen werden korrekt dargestellt.

Schokohase 9. Aug 2019 15:16

AW: Delphi und Linux - Encoding-Salat?
 
Irgendwie verstehe ich dich glaube ich nicht.

Wenn du egal wo immer den gleichen Standard-Wert für das Encoding haben möchtest, falls es keinen BOM gibt, dann verwende die overload Variante der Methode und gib das gewünschte Encoding mit
Delphi-Quellcode:
LEncoding := nil;
LOffset := TEncoding.GetBufferEncoding( LBuffer, LEncoding, TEncoding.ANSI { dieses Default-Encoding verwenden } );
und das Kas is gerollt.

Oder etwa nicht?

knaeuel 9. Aug 2019 15:42

AW: Delphi und Linux - Encoding-Salat?
 
ja, solange ich den Aufruf selber mache, passts. Problematisch wirds, wenn innerhalb der Delphi-Bibliotheken "GetBufferEncoding" aufgerufen wird. Dann kann ich nicht verhindern, dass (unter Linux) ANSI-Dateien fälschlicherweise als UTF-8 betrachtet werden.

Ich habe ja jetzt auch einen funktionierenden Workaround, seit ich gesehen habe, dass ich eine XML-Datei auch per LoadFromStream in die TXMLDocument-komponente bekomme. Da kann ich nämlich die Kodierung auch mitangeben.

Zum Feststellen der Kodierung einer Datei musste ich die Klasse TEncoding erben und die Methoden "GetBufferEncoding" und "GetDefault" überschreiben.

Durch diese neue Klasse TMyEncoding kann ich jetzt auch unter Linux ANSI-Dateien konvertieren und als z.B. UTF-8 oder UTF-16 LE/BE speichern. Das war vorher nicht möglich, weil eben auch wieder von einem falschen Quellformat ausgegangen wurde.

TurboMagic 9. Aug 2019 16:29

AW: Delphi und Linux - Encoding-Salat?
 
Zitat:

Zitat von knaeuel (Beitrag 1440567)
ja, solange ich den Aufruf selber mache, passts. Problematisch wirds, wenn innerhalb der Delphi-Bibliotheken "GetBufferEncoding" aufgerufen wird. Dann kann ich nicht verhindern, dass (unter Linux) ANSI-Dateien fälschlicherweise als UTF-8 betrachtet werden.

Ich habe ja jetzt auch einen funktionierenden Workaround, seit ich gesehen habe, dass ich eine XML-Datei auch per LoadFromStream in die TXMLDocument-komponente bekomme. Da kann ich nämlich die Kodierung auch mitangeben.

Zum Feststellen der Kodierung einer Datei musste ich die Klasse TEncoding erben und die Methoden "GetBufferEncoding" und "GetDefault" überschreiben.

Durch diese neue Klasse TMyEncoding kann ich jetzt auch unter Linux ANSI-Dateien konvertieren und als z.B. UTF-8 oder UTF-16 LE/BE speichern. Das war vorher nicht möglich, weil eben auch wieder von einem falschen Quellformat ausgegangen wurde.

Ok, dann erstelle dopch mal einen QP Report. Dan sehen wir ja auch bald wie EMBT darauf reagiert.


Alle Zeitangaben in WEZ +1. Es ist jetzt 18:21 Uhr.

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