AGB  ·  Datenschutz  ·  Impressum  







Anmelden
Nützliche Links
Registrieren
Zurück Delphi-PRAXiS Projekte Vorstellung Unit: File encoding detector
Thema durchsuchen
Ansicht
Themen-Optionen

Vorstellung Unit: File encoding detector

Ein Thema von LTE5 · begonnen am 19. Nov 2017 · letzter Beitrag vom 15. Jan 2018
Antwort Antwort
Seite 1 von 2  1 2      
Wosi

Registriert seit: 29. Aug 2007
59 Beiträge
 
#1

AW: Vorstellung Unit: File encoding detector

  Alt 19. Nov 2017, 16:27
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 Wikipedia.
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
  Mit Zitat antworten Zitat
LTE5

Registriert seit: 13. Nov 2017
355 Beiträge
 
Delphi 10.2 Tokyo Starter
 
#2

AW: Vorstellung Unit: File encoding detector

  Alt 19. Nov 2017, 16:40
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:
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.
Genau das will ich ja aber. Ich brauche das Encoding, eben damit ich die Daten lesen kann. Sonst würde ich mir die ganze Mühe nicht machen.

Zitat:
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.
Welche Sequenz ist das denn genau und wie prüfe ich danach?

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:
- 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.
Ich werde mich mal dran versuchen und TEncodingDetect.IsStreamUnicode anpassen. Könnte jedenfalls schwer werden, denn von dem Wikipedia-Artikel verstehe ich nichts.

Geändert von LTE5 (19. Nov 2017 um 16:55 Uhr)
  Mit Zitat antworten Zitat
Benutzerbild von Uwe Raabe
Uwe Raabe

Registriert seit: 20. Jan 2006
Ort: Lübbecke
11.654 Beiträge
 
Delphi 12 Athens
 
#3

AW: Vorstellung Unit: File encoding detector

  Alt 19. Nov 2017, 16:56
Genau das will ich ja aber. Ich brauche das Encoding, eben damit ich die Daten lesen kann. Sonst würde ich mir die ganze Mühe nicht machen.
Die gezeigte Funktion liefert dir ja auch den String korrekt decodiert zurück. Den kannst du dann ja weiter verarbeiten ohne dich noch um das Encoding der Datei kümmern zu müssen.

Welche Sequenz ist das denn genau und wie prüfe ich danach?
Kannst du bei Wikipedia nachsehen: Zulässige Bytes und ihre Bedeutung
Uwe Raabe
Certified Delphi Master Developer
Embarcadero MVP
Blog: The Art of Delphi Programming
  Mit Zitat antworten Zitat
LTE5

Registriert seit: 13. Nov 2017
355 Beiträge
 
Delphi 10.2 Tokyo Starter
 
#4

AW: Vorstellung Unit: File encoding detector

  Alt 19. Nov 2017, 16:59
Zitat:
Die gezeigte Funktion liefert dir ja auch den String korrekt decodiert zurück.
Leider bringt mir der String aber nicht, wenn es um das Encoding geht, welches ich bei TMemIniFile brauche.

Zitat:
Kannst du bei Wikipedia nachsehen: Zulässige Bytes und ihre Bedeutung
Auf soviele verschiedene Bytes kann ich doch gar nicht prüfen.
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;

Geändert von LTE5 (24. Nov 2017 um 13:33 Uhr)
  Mit Zitat antworten Zitat
Benutzerbild von Uwe Raabe
Uwe Raabe

Registriert seit: 20. Jan 2006
Ort: Lübbecke
11.654 Beiträge
 
Delphi 12 Athens
 
#5

AW: Vorstellung Unit: File encoding detector

  Alt 19. Nov 2017, 17:47
Auf soviele verschiedene Bytes kann ich doch gar nicht prüfen.
Oder sind das insgesamt nur 6?
Angenommen du liest jedes Byte nacheinander, dann sind alle Bytes im Bereich $00 - $7F gültige Zeichen. Ist das nicht der Fall, musst du prüfen, ob eine UTF-8 Sequenz folgt. Diese wird eingeleitet von einem Byte im Bereich $C2..$F4 - alles andere wäre ein Fehler. Die Länge der UTF-8 Sequenz wird durch dieses Byte bestimmt. Liegt es im Bereich $C2..$DF folgt ein weiteres Byte, liegt es im Bereich $E0..$EF folgen zwei weitere Bytes und liegt es im Bereich $F0..$F4 folgen drei weitere Bytes. Alle diese weiteren Bytes müssen im Bereich $80..$BF liegen. Nachdem du die entsprechende Menge weiterer Bytes gelesen hast, beginnt das Spiel wieder von vorn. Liegt ein Byte außerhalb des gerade erwarteten Bereichs liegt ein Fehler vor oder es handelt sich nicht um eine UTF-8 Codierung.

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;
Uwe Raabe
Certified Delphi Master Developer
Embarcadero MVP
Blog: The Art of Delphi Programming
  Mit Zitat antworten Zitat
LTE5

Registriert seit: 13. Nov 2017
355 Beiträge
 
Delphi 10.2 Tokyo Starter
 
#6

AW: Vorstellung Unit: File encoding detector

  Alt 19. Nov 2017, 17:54
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?

Geändert von LTE5 (19. Nov 2017 um 18:00 Uhr)
  Mit Zitat antworten Zitat
Benutzerbild von himitsu
himitsu

Registriert seit: 11. Okt 2003
Ort: Elbflorenz
44.379 Beiträge
 
Delphi 12 Athens
 
#7

AW: Vorstellung Unit: File encoding detector

  Alt 19. Nov 2017, 17:58
Eine Prüfroutine für UTF-8 könnte etwa so aussehen (ungetestet)
Billigvariante: (testet nicht nur, sondern wandelt um und braucht zusätzlichen etwas RAM)
Delphi-Quellcode:
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;
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.
Ein Therapeut entspricht 1024 Gigapeut.
  Mit Zitat antworten Zitat
Wosi

Registriert seit: 29. Aug 2007
59 Beiträge
 
#8

AW: Vorstellung Unit: File encoding detector

  Alt 19. Nov 2017, 18:06
Eine Prüfroutine für UTF-8 könnte etwa so aussehen (ungetestet):
Vom Ansatz her gut aber du prüfst nicht, ob eine Multibyte-Sequenz vorzeitig abgebrochen wird.
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;
  Mit Zitat antworten Zitat
LTE5

Registriert seit: 13. Nov 2017
355 Beiträge
 
Delphi 10.2 Tokyo Starter
 
#9

AW: Vorstellung Unit: File encoding detector

  Alt 19. Nov 2017, 18:25
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.

Geändert von LTE5 (19. Nov 2017 um 18:47 Uhr)
  Mit Zitat antworten Zitat
Benutzerbild von Uwe Raabe
Uwe Raabe

Registriert seit: 20. Jan 2006
Ort: Lübbecke
11.654 Beiträge
 
Delphi 12 Athens
 
#10

AW: Vorstellung Unit: File encoding detector

  Alt 19. Nov 2017, 20:53
Vom Ansatz her gut aber du prüfst nicht, ob eine Multibyte-Sequenz vorzeitig abgebrochen wird.
Gut erkannt
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;
Uwe Raabe
Certified Delphi Master Developer
Embarcadero MVP
Blog: The Art of Delphi Programming
  Mit Zitat antworten Zitat
Antwort Antwort
Seite 1 von 2  1 2      


Forumregeln

Es ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.

BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus.
Trackbacks are an
Pingbacks are an
Refbacks are aus

Gehe zu:

Impressum · AGB · Datenschutz · Nach oben
Alle Zeitangaben in WEZ +1. Es ist jetzt 02:15 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