![]() |
Vorstellung Unit: File encoding detector
Ich möchte euch gerne meine Arbeit aus den vergangenen Tagen vorstellen.
Ich bin kein Profi und habe mich erst vor wenigen Tagen in Streams eingelesen. Ich brauchte eine Lösung, um mehr oder weniger zuverlässig das Encoding einer Datei herausfinden zu können. Über BOM ist das ja leider nicht immer feststellbar, daher habe ich eine andere Lösung entworfen. Sie ist weder professionell noch gut, aber sie funktioniert. Es ist mehr oder weniger eine Zusammenstellung aus Dingen die ich im Internet gefunden habe. Daher bitte ich um Feedback! Ihr könnt die Unit auch gerne kopieren, anpassen und hier im Beitrag wieder posten. Der Aufruf ist einfach
Delphi-Quellcode:
So ist es auch möglich
var IsUnicode: Boolean;
begin IsUnicode := TEncodingDetect.IsFileUnicode('pfad-zur-datei.txt');
Delphi-Quellcode:
Memo1.Lines.Clear;
Memo1.Lines.Add('File unicode: ' + BoolToStr(TEncodingDetect.IsFileUnicode('pfad-zur-datei.txt'), True)); Memo1.Lines.Add('String unicode: ' + BoolToStr(TEncodingDetect.IsTextUnicode('ʥ'), True)); Memo1.Lines.Add('String unicode: ' + BoolToStr(TEncodingDetect.IsTextUnicode('ABC'), True)); Memo1.Lines.Add('File content: ' + TFile.ReadAllText('pfad-zur-datei.txt', TEncodingDetect.GetEncoding('pfad-zur-datei.txt'))); |
AW: Vorstellung Unit: File encoding detector
Hallo-
Wenn du statt (oder zumindest zusätzlich zu) String-Pfadangaben den Benutzer auch
Delphi-Quellcode:
reinstecken lässt, lassen sich viel besser Unit-Tests dafür schreiben :thumb:
TStream
Es wäre beispielsweise viel einfacher Daten aus einem ZIP-Archiv, einem Email-Anhang oder einem Netzwerk-Stream zu analysieren. So müsste ich ihn erst einmal auf der Platte speichern oder in einen zusätzlichen String speichern. Als Kritik hätte ich sonst noch die Enumeration
Delphi-Quellcode:
- Mir wäre lieber ich bekäme eine frisch erstellte
TUnicodeType
Delphi-Quellcode:
-Instanz zurück. Wahrscheinlich Geschmackssache.
TEncoding
PS: Den Typ "NoBOM" zurückzubekommen wenn die angegeben Datei nicht existiert finde ich ehrlich gesagt nicht gut. Da gehört eine Exception geworfen. |
AW: Vorstellung Unit: File encoding detector
Danke erst einmal.
TEncoding wäre mir auch lieber. Was TEncoding aber nicht kennt sind die UTF32-Varianten von Endian. Oder zählt UTF32 unter TEncoding.Unicode? Was mich auch stört ist, dass ich bei nur einem Aufruf von IsFileUnicode 2x TBytesStream erzeuge, wenn es ganz durch geht. Das könnte man mit einem Konstruktor und Destruktor-Konstrukt lösen. Aber dann wäre es kein Einzeiler mehr. Was mir auch nicht gefällt ist die Art und Weise des Vergleichs auf das BOM. Besser bekomme ich es leider nicht hin. |
AW: Vorstellung Unit: File encoding detector
Meinst du den Vergleich mit den hardkodierten Werten? Ich würde stattdessen
Delphi-Quellcode:
nehmen?
TEncoding.UTF8.GetPreamble()
Ich habe so etwas ähnliches in eine Nummer kleiner auch irgendwo herumliegen, aber nur auf der Arbeit. Da kann ich ab nächster Woche ja mal schauen zum Vergleichen... |
AW: Vorstellung Unit: File encoding detector
Ich habe noch zwei interessante Links dazu:
![]() ![]() |
AW: Vorstellung Unit: File encoding detector
|
AW: Vorstellung Unit: File encoding detector
Zitat:
Zitat:
Wäre also eine Lösung für alles, bis auf UTF32. Ich füge es oben gleich mal an. |
AW: Vorstellung Unit: File encoding detector
Zitat:
Nur so als Hinweis: Die Unit System.Character enthält ein paar Methoden für UTF-32 Zeichen und in System ist auch ein Typ UCS4String definiert, der allerdings nichts mit einem üblichen Delphi-String gemein hat. |
AW: Vorstellung Unit: File encoding detector
Irgendwie habe ich den Eindruck meine Unit ist mehr als nutzlos in diesem Encoding-Labyrinth.
Aber mehr als "erraten" kann man ja eh nicht. Also versuche ich jedenfalls das. Wenn jemand eine bessere Unit bauen kann, würde sich jemand dazu bereit erklären? Zitat:
# Ich habe die Unit in #1 nochmal angepasst. # Ich habe die Unit in #1 noch einmal angepasst. Rückgabewert ist jetzt TEncoding. Ich habe lange getestet. Wenn ich nicht gerade in den Kopfdaten einer Datei rumwühle und komische Werte eintrage, wird die Datei immer korrekt gelesen. # Einen Fall habe ich doch gefunden. Wenn man eine utf-8-Datei ohne BOM prüft und kein Default-Encoding angibt, kommt natürlich ANSI zurück und é kann nicht dargestellt werden. Deswegen gibt es nun eine überladene Version von TEncodingDetect.GetFileEncoding(). |
AW: Vorstellung Unit: File encoding detector
Hmm..
Zitat:
ASCII entspricht 7 Bit = 0-127 ANSI entspricht 8 Bit = 0-255 ![]() Somit kann ein gültiges ANSI-Zeichen auch als Zahl 240 sein.. ;) |
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; |
AW: Vorstellung Unit: File encoding detector
Ich habe mir eben mal einen HexEditor besorgt.
Damit verstehe ich das alles vielleicht besser wenn ich dann noch alles mit Haltepunkten durchgehe oder anderweitig mir anzeigen lasse was gerade los ist. Der Hex-Editor hat ein wenig, aber nicht komplett, Licht ins Dunkle gebracht. Interessant war nur zu sehen, dass mein HexEditor keine UTF-8 mit BOM-Dateien lesen konnte. Muss vielleicht einen neuen suchen. Darf ich euch meine neue Version der Unit MIT (!) Codefragmenten von euch zeigen? Hinzugefügt habe ich Funktionen die Streams entgegennehmen können. Die haben mir intern auch bei so manchem Problem geholfen. Was Himitus "Billigvariante" mit UTF8ToString angeht: das funktioniert bestimmt wie gewünscht. Da ich aber nur das boolsche Resultat benötige und nichts umwandeln muss, lasse ich die Version raus. |
AW: Vorstellung Unit: File encoding detector
Alle Änderungen wurden übernommen.
Alle Funktionen nehmen nun auch Streams entgegen. Es gibt einen kleinen Aufbaufehler, den ich notgedrungen mit der Variablen SkipBOMCheck lösen musste. Sonst würde sich eine Funktion immer wieder selber aufrufen. Grund dafür, dass GetFileEncoding aber in IsStreamUnicode steckt ist, weil IsStreamUnicode am niedrigsten Punkt ist und von allen übergeordneten Funktionen angesprochen wird. |
AW: Vorstellung Unit: File encoding detector
Zitat:
Ist mir dann auch noch so in den Sinn gekommen. Ich würde das dann aber doch etwas kompakter schreiben:
Delphi-Quellcode:
function IsUTF8(Bytes: TBytes): Boolean;
var B: Byte; weitere: 0..3; begin weitere := 0; for B in Bytes do begin if weitere > 0 then begin if not (B in [$80..$BF]) then Exit(False); Dec(weitere); end else begin case B of $00..$7F: ; { ASCII } $C2..$DF: weitere := 1; $E0..$EF: weitere := 2; $F0..$F4: weitere := 3; else Exit(False); end; end; end; Result := True; end; |
AW: Vorstellung Unit: File encoding detector
Zitat:
Wenn IsStreamUnicode aufgerufen wird, wird als erstes GetFileEncoding aufgerufen. Findet GetFileEncoding kein BOM, wird wieder IsStreamUnicode aufgerufen. IsStreamUnicode läuft also im schlimmsten Fall zweimal durch. Hat jemand eine Idee wie man diese beiden unschönen Fehler weg bekommt? |
AW: Vorstellung Unit: File encoding detector
Ich hoffe dass kommt jetzt nicht als super-harte Kritik wie von Marcel Reich-Ranicki, aber ich fange an hieran zu zweifeln. Ich wollte ein paar Tests schreiben - Es scheitert schon wenn ich einen völlig offensichtlichen Unicode-String wie
Delphi-Quellcode:
reinstecke und bekomme für
╰( ͡° ͜ʖ ͡° )つ──☆*:・゚
Delphi-Quellcode:
ein
IsTextUnicode
Delphi-Quellcode:
zurück.
False
Ganz abgesehen davon dass der dort erstellte
Delphi-Quellcode:
nicht freigegeben wird.
TStringStream
|
AW: Vorstellung Unit: File encoding detector
Deswegen habe ich im ersten Beitrag im Feedback und Korrekturen gebeten.
Zitat:
Wie gesagt. Ich würde mich sehr freuen wenn ihr mir helft, dass es funktioniert. Mehr als das was im ersten Beitrag steht schaffe ich nicht. |
AW: Vorstellung Unit: File encoding detector
Die Funktion
Delphi-Quellcode:
kann so nicht funktionieren, da die Steuerzeichen nicht vorhanden sind, auch im Stream davon nicht. Des Weiteren ist intern der Delphi-string immer Unicode (Der Standardtyp für Strings ist UnicodeString). ;-)
IsTextUnicode
...:cat:... |
AW: Vorstellung Unit: File encoding detector
Edit
ich habe die Funktion IsTextUnicode jetzt komplett entfernt. Ich brauchte sie eh nicht. Dafür funktioniert bei mir der Rest wenigstens wie erwartet. Feedback trotzdem gerne willkommen, denn schön ist die Unit meiner Meinung nach nicht. |
AW: Vorstellung Unit: File encoding detector
Ich habe gerade eben Version 0.3 in den ersten Beitrag gepackt. Ich habe ein wenig aufgeräumt und umgebaut.
Eigentlich sollte nun einiges funktionieren. Die BOM-Prüfung wird bisher nur von IsFileUnicode() genutzt. Nachtrag: sollte etwas nicht funktionieren und das sogar sehr offensichtlich, und ich habe es nicht gesehen, dann bitte ich um ein paar Ohrfeigen. |
AW: Vorstellung Unit: File encoding detector
Kleiner Verbesserungsvorschlag: Bei der Methode IsUtf8 sollte der Parameter besser mit const gekennzeichnet werden.
|
AW: Vorstellung Unit: File encoding detector
Ich musste deine Antwort zweimal lesen. Denn ich habe eigentlich noch mit Kritik gerechnet und dass irgendetwas, was absolut funktionieren muss, nicht funktioniert.
Aber wenn es bisher nur das ist :thumb: |
AW: Vorstellung Unit: File encoding detector
Ich arbeite gerade an einem Tool namens "FileID".
Es identifiziert enorm viel Datei-Formate, über 3000 verschiedene binärer art. Unter anderem auch Text dateien und deren "Encoding", was ich bei mir so realisiert hab: ich les an offset 0 der datei das erste byte und bestimme dadurch mit was es codiert wurde. Als referenz nahm ich mir Notepad++ und erstellte pro format ein paar dateien. (des weiteren prüft mein tool ob irgendwelche nicht-lesbaren (binär) zeichen enthalten sind um generell zu bestimmen ob's ne text-datei ist) Ich hoff es hilft. (ist halt ne andere methode um ans ziel zu gelangen) |
AW: Vorstellung Unit: File encoding detector
Zitat:
Bei UTF-8 ist das BOM genau 3 Byte lang. Im Prinzip ist das mit dem BOM recht einfach zu verstehen, denn es ist zufällig das Unicodezeichen #$FEFF welches mit dem jeweiligen Encoding (Codepage) codiert wurde und was praktisch an erster Stelle des decodierten Unicode-Textes steht. siehe $FFFE ![]() ![]() Seinen Namen hat das Byte-Order-Mark von den Codes $FFFE und $FEFF der UTF-16 / UCS2 in Big Endian und Little Endian. Bei vielen "Binär"-Dateien ist das Magic-Byte, auch wenn es sich "Byte" nennt oft 2 bis 4 Byte lang. z.B. "PE" bei Portable Execute, also den kompilierten EXE und DLL (und BPL, welche auch "nur" aufgemotzte DLL sind) Und was sind "nicht-lesbare (binär) zeichen"? Bei Chinesisch, Russisch, Swahili und Dergleichen, ist für "uns" Vieles nicht lesbar. :stupid: |
AW: Vorstellung Unit: File encoding detector
nicht lesbar = code $00 - $21 (oder war's $23?) zum beispiel.
mit nicht lesbar mein ich man sollte nicht versuchen diese ascii codes darzustellen, sie enthalten keine "schriftzeichen", egal von wo. und ich start meine identifizierung mit dem ersten byte, danach geht identifierung weiter mit zweitem dritten usw. es gibt viele dateien deren signatur mit $FF beginnt. ich hoff es hilft. ps: ich gab ja keine werte zum vergleich an, ich meint halt nur das ich signaturen per byte abfrage kontrolliere, und nicht per CLASS definitionen auslese. |
AW: Vorstellung Unit: File encoding detector
Moin,
weitere Ideen sind ja grundsätzlich nichts schlechtes. Aber ich bin nicht sicher, ob Du dieses Thema gelesen hast. Es geht hier gerade auch um Dateien, die keine entsprechende Kennung aufweisen. Der von Dir vorgestellte Ansatz wurde auf Seite 2 diskutiert und verworfen. Lass Dich nicht entmutigen, das kann passieren - aber wirf bitte vorher kurz einen Blick auf die Themen, zu denen Du etwas schreiben möchtest. |
Alle Zeitangaben in WEZ +1. Es ist jetzt 19:57 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