![]() |
AW: Vorstellung Unit: File encoding detector
Wie genau müsste man denn dann nachprüfen?
Einfach bis 255 klingt ja zu einfach. |
AW: Vorstellung Unit: File encoding detector
Die Problematik liegt eigentlich in der Unterscheidung zwischen ANSI und UTF-8 ohne BOM. Welche CodePage bei ANSI verwendet werden soll kannst du eh kaum raus finden, wenn du keine Informationen über die Art des Inhaltes hast (manche Zeichen kommen in einer bestimmten Art Text halt nicht vor und sind ein Trigger für eine falsche Codierung). Allerdings beginnen in UTF-8 alle Zeichen > #127 mit einer bestimmten Sequenz. Sind also Zeichen > #127 vorhanden, die nicht mit einer dieser UTF-8 Sequenzen beginnen, handelt es sich offenbar nicht um ein UTF-8 Encoding.
Ein sehr einfacher Ansatz um ANSI und UTF-8 zu unterscheiden wäre z.B. einfach erst mit TEncoding.UTF8 (BOM oder nicht erkennt er automatisch) einzulesen und falls das eine Exception wirft eben mit TEncoding.ANSI zu lesen. Bei dieser Vorgehensweise braucht man auch nicht erst das Encoding ermitteln, sondern bekommt das beim Lesen gleich mit. Das spart ein erneutes Durchlaufen zur eigentlichen Verarbeitung der Daten, denn wozu brauche ich das Encoding der Daten, wenn ich sie danach nicht damit lesen will. Das könnte dann in etwa so aussehen:
Delphi-Quellcode:
function StreamToString(Stream: TStream): string;
var enc: TEncoding; reader: TStreamReader; savePosition: Int64; begin savePosition := Stream.Position; try { ANSI als letztes, denn das klappt immer } for enc in TArray<TEncoding>.Create(TEncoding.UTF8, TEncoding.ANSI) do begin Stream.Position := savePosition; reader := TStreamReader.Create(Stream, enc, false); try try result := reader.ReadToEnd; { Wenn es geklappt hat, Schleife verlassen } Break; except on EEncodingError do ; end; finally reader.Free; end; end; finally Stream.Position := savePosition; end; end; |
AW: Vorstellung Unit: File encoding detector
Für den Fall, dass es keinen BOM gibt:
Gibt es nur Zeichen bis #127, dann ist es sicher den Stream als ASCII-String zu interpretieren. Liegen einzelne Byte jedoch im Wertebereich zwischen #128 und #255 dann wird es komplizierter. Dann könnte es sich um UTF8 oder ANSI oder ein anderes lokales Format handeln. Es gibt dabei leider kein 100% sicheres Vorgehen um das korrekte Encoding zu ermitteln. Es existieren lediglich Vorgehensweisen um ein möglichst gutes Ergebnis zu erzielen. Ich musste mal ein Problem lösen, bei dem eine Software muss regelmäßig entscheiden musste, ob eine Datei UTF8 oder ANSI beinhaltet. So habe ich es damals gelöst: - Ist ein BOM-Header vorhanden, dann nimm das passende Encoding für den BOM-Header - Ansonsten untersuche den Bytestream auf gültige UTF8-Multi-Byte-Zeichen. Gibt es eine Byte-Sequenz, die nicht UTF8-Konform ist, dann nimm ANSI. Entspricht der Byte-Stream hingegen bis zum Ende gültigem UTF8, dann nimm UTF8. Die Definition von Multi-Byte-Zeichen gibt es bei ![]() Das beschriebene Vorgehen funktioniert generell ganz gut. Allerdings scheitert es, wenn - wenn sich mindestens ein ungültiges Multi-Byte-Zeichen in einer ansonsten gültigen UTF8-Datei befindet (von Text-Editoren werden diese Zeichen in der Regel als "�" dargestellt) - wenn der Dateiinhalt weder ANSI noch UTF8 ist |
AW: Vorstellung Unit: File encoding detector
Habe wieder viel zu lesen! Melde mich gleich.
Ich habe in der Zwischenzeit Support für Streams eingebaut. Schreibe ich gleich in Beitrag #1. Zitat:
Zitat:
Sollte man demnach hier unten also besser bis 255 prüfen und ab 127 zusätzlich diese Sequenz prüfen?
Delphi-Quellcode:
class function TEncodingDetect.IsStreamUnicode(const Stream: TStream): Boolean;
var i: Integer; B: Byte; begin Result := False; if Stream.Size = 0 then Exit; for i := 0 to Stream.Size - 1 do begin Stream.ReadData(B, Sizeof(B)); Result := Ord(B) > 127; if Result then Break; end; end; Zitat:
|
AW: Vorstellung Unit: File encoding detector
Zitat:
Zitat:
![]() |
AW: Vorstellung Unit: File encoding detector
Zitat:
Zitat:
Oder sind das insgesamt nur 6? Ich habe mich mal so daran versucht. Ist das so richtig?
Delphi-Quellcode:
var i: Integer; B: Byte; Bytes: TBytes;
begin Stream.Position := 0; for i := 0 to Stream.Size - 1 do begin SetLength(Bytes, 2); Stream.Read(Bytes, Length(Bytes)); if (Bytes = TBytes.Create($C0, $C1)) or (Bytes = TBytes.Create($F5, $F7)) or (Bytes = TBytes.Create($F8, $FB)) or (Bytes = TBytes.Create($FC, $FD)) or (Bytes = TBytes.Create($FE, $FF)) then ShowMessage('Ungültige Sequenz gefunden.'); end; end; end; |
AW: Vorstellung Unit: File encoding detector
Zitat:
Eine Prüfroutine für UTF-8 könnte etwa so aussehen (ungetestet):
Delphi-Quellcode:
function IsUTF8(Bytes: TBytes): Boolean;
var B: Byte; weitere: 0..3; begin weitere := 0; for B in Bytes do begin case B of $00..$7F: ; { ASCII } $80..$BF: begin if weitere > 0 then begin Dec(weitere); end else begin Exit(False); end; end; $C2..$DF: weitere := 1; $E0..$EF: weitere := 2; $F0..$F4: weitere := 3; else Exit(False); end; end; Result := True; end; |
AW: Vorstellung Unit: File encoding detector
Spätestens hier resigniere ich.
Ich muss mir das heute Abend mal in Ruhe angucken. Speziell dieses weitere mit inkrementieren und dekrementieren. Ist es möglich von irgendeiner Quelle UTF-8-Dateien mit absichtlichen Fehlern zu bekommen? |
AW: Vorstellung Unit: File encoding detector
Zitat:
Delphi-Quellcode:
Der Code in der System-Unit wirft keine Exception, sondern gibt einfach nichts zurück (Leerstring), wenn es man kein valides UTF-8 rein gibt.
function IsUTF8(Bytes: RawByteString{oder TBytes}): Boolean;
begin Result := {(Bytes = '') and} (UTF8ToString(Bytes) <> ''); // inkl. dem Auskommentierten, wird auch ein Leerstring als UTF-8 erkannt, auch wenn "garnichts" im String ist. end; |
AW: Vorstellung Unit: File encoding detector
Zitat:
Ebenfalls ungetesteter Fix:
Delphi-Quellcode:
function IsUTF8(Bytes: TBytes): Boolean;
var B: Byte; weitere: 0..3; begin weitere := 0; for B in Bytes do begin case B of $00..$7F: if weitere > 0 then Exit(False); { ASCII } $80..$BF: begin if weitere > 0 then begin Dec(weitere); end else begin Exit(False); end; end; $C2..$DF: if weitere > 0 then Exit(False) else weitere := 1; $E0..$EF: if weitere > 0 then Exit(False) else weitere := 2; $F0..$F4: if weitere > 0 then Exit(False) else weitere := 3; else Exit(False); end; end; Result := True; end; |
Alle Zeitangaben in WEZ +1. Es ist jetzt 19:58 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