Marian Aldenhövel

Portrait


Wie bringt man FileType
einen neuen Dateityp bei?

Zurück zur FileType-Hauptseite

Ein Dateiformat in FileType entspricht einer von TFileTypeFormat abgeleiteten Klasse.  Eine solche Klasse kann zwar auch mehrere verwandte Formate behandeln, im Allgemeinen ist das aber eine 1:1-Beziehung.

Die folgende Anleitung führt schrittweise anhand des GIF-Formats vor, wie man bei der Implementation einer solchen Klasse vorgeht. Korrekturen, Vorschläge und allgemeine Hinweise sind wie immer willkommen:

0) Voraussetzungen

Voraussetzungen für die Implementation eines neuen Dateiformats für FileType sind

1) Vorgängerklasse wählen

Ein ganz neues Dateiformat wird von TFileTypeFormat direkt abgeleitet. Speziellere Formate können von den Klassen für allgemeinere abgeleitet werden. Der Registrier-Mechanismus kann dann dafür sorgen, daß die speziellen Formate vor den allgemeinen geprüft werden.

Das GIF-Beispiel wird direkt von TFileTypeFormat abgeleitet. Wir legen also eine Unit FileTypeGIF.pas an:

  unit FileTypeGIF;

  {$INCLUDE FILETYPE.INC}
  { This unit implements GIF-File support for the FileType program
    Author: marian@mba-software.de (Marian Aldenhoevel) }

  interface

  uses Windows,SysUtils,Classes,FileTypeFormats;

  type TFileTypeGIF=class(TFileTypeFormat)

Wesentlich ist hier das Einbinden der unit FileTypeFormats, die TFileTypeFormat deklariert. Das Einbinden von FILETYPE.INC ist empfohlen, dort können projektglobale Einstellungen getroffen werden.

2) Methoden überschreiben

Um die spezifische Funktionalität unterzubringen sollte eine neue FileFormat-Klasse (mindestens) folgende Methoden überschreiben:

  protected
    procedure AnalyseData; override;
    function GetDescription:string; override;
    class function ContextExtensions:string; override;
  public
    class function TestOrder:string; override;
    class function DisplayName:string; override;
    class function StreamIsThisFormat(aStream:TStream):boolean; override;
  end;

In den weiteren Schritten werden wir jetzt jede dieser Methoden implementieren. Deshalb kommt als nächstes direkt das

  implementation  

3) DisplayName

  class function TFileTypeGIF.DisplayName:string;
  begin
    Result:='Compuserve Graphics Interchange Format (GIF)';
  end;

Diese Methode gibt einfach einen string zurück, der das Dateiformat in menschenlesbarer Form beschreibt. Dieser Text wird zum Beispiel in der Listbox der aktiven Formate angezeigt.

4) TestOrder

  class function TFileTypeGIF.TestOrder:string; 
  begin
    Result:='GIF';
  end;

TestOrder liefert ebenfalls einen string. Dieser string dient der Sortierung in der Liste der registrierten Formate. Hier ist also etwas mehr Sorgfalt angesagt.

Ziel ist, daß speziellere Formate vor allgemeineren gestetet werden. Wir werden GIF-Dateien anhand des Beginns 'GIF87a' bzw. 'GIF89a' identifizieren. Angenommen es gäbe nun eine andere Formatklasse, deren Dateien ebenfalls mit 'GIF87a' beginnen, aber danach anhand anderer Kriterien weiter spezialisiert werden, sollte diese Formatklasse vor unserer TFileTypeGIF die Chance bekommen die Datei zu prüfen. Daher müsste jene hypothetische Klasse einen Sortorder-Wert bekommen der sie vor TFileTypeGIF einsortiert.

Bessere Ideen sind willkommen.

Im Beispiel nehmen wir einfach den String GIF und warten was kommt.

5) ContextExtensions

  class function TFileTypeGIF.ContextExtensions:string;
  begin
    Result:='.gif';
  end;

Diese Methode liefert einen String von Datei-Extensions zurück.

Frage: FileType analysiert aber doch den Dateiinhalt, wozu braucht es dann Dateiextensions? Antwort: Um das Explorer-Kontextmenü zu beschicken.

In der Rückgabe von ContextExtensions werden mehrere Datei-Extensions durch Spaces getrennt angegeben, hier ist das Beispiel für JPEG:

  class function TFileTypeJPEG.ContextExtensions:string;
  begin
    Result:='.jif .jfif .jpg .jpe .jpeg';
  end;

Wenn das Dateiformat in FileType in der Listbox der aktiven Formate angekreuzt wird assoziiert FileType alle diese Extensions mit dem neuen Kontextmenü-Verb Information, das dann FileType aufruft (Der Name des Verbs kann mit der Methode ContextVerb Format-spezifisch gesteuert werden).

6) StreamIsThisFormat

Hier wird es ernst:

  class function TFileTypeGIF.StreamIsThisFormat(aStream:TStream):boolean;
  var H:string;
      OldPosition:integer;
  begin
    OldPosition:=aStream.Position;
    try
      try
        SetLength(H,6);
        aStream.ReadBuffer(H[1],Length(H));
        if H='GIF87a'
          then Result:=True
          else
            if H='GIF89a'
              then Result:=True
              else Result:=False;
      except
        Result:=False;
      end;
    finally
      aStream.Seek(OldPosition,soFromBeginning);
    end;
  end;

Die Methode StreamIsThisFormat ist der Entscheider ob eine Datei von einem gegebenen Typ ist oder nicht. Ihr wird ein Stream mit den Daten vorgelegt. Diese Methode soll nun gerade so viel Information lesen um zu entscheiden ob die Daten im für die Klasse richtigen Format vorliegen. Dann liefert sie True, sonst False.

Im Falle von GIF-Dateien lesen wir die ersten 6 Bytes der Datei und sehen nach, ob dort einer der beiden Kennstrings steht. Falls ja, nennen wir das GIF, falls nein nicht. Geben also True oder False zurück.

Zuletzt, und das ist wichtig, bringen wir den Eingabe-Stream wieder dorthin zurück wo er war damit ein anderes Format oder die Methode AnalyseData seine Daten korrekt vorfindet.

Vorsicht! Die ist eine Klassenmethode, sie hat keinen Zugriff auf Felder. Deshalb setzen wir hier auch nicht direkt FGIFType, sondern machen das in

7) AnalyseData

Dies ist die Methode, die nach erfolgreichem "Zuschlag" in StreamIsThisFormat, die Daten liest und die für die Zusammenfassung interessanten Informationen extrahiert.

Ein wesentlicher Unterschied zu StreamIsThisFormat besteht darin, daß es keine Klassenmethode ist. Zu diesem Zeitpunkt "gehört" der Stream also der Klasse, wird über das Feld Stream referenziert,  und sie darf damit machen was sie will. Sie braucht ihn also auch nicht wieder zurückzuspulen. Außerdem hat die Methode Zugriffe auf die anderen Felder der Klasse.

Die Implementation ist die längste der überschriebenen Methoden:

  procedure TFileTypeGIF.AnalyseData;
  var He:string;
      lp,tp,w,h,lsw,lsh:word;
      c,pf,BlkSize,GCTsize:byte;
  begin
    FSizeBytes:=Stream.Size;

    { Read GIF-Type 'GIFxxa' }
    SetLength(He,6);
    Stream.ReadBuffer(He[1],Length(He));
    if He='GIF87a'
      then FGIFType:=gt87a
      else
        if He='GIF89a'
          then FGIFType:=gt89a
          else
            begin
              { This should not happen }
              FGIFType:=gtNoGIF;
              exit;
            end;
    { Examine the Logical Screen Descriptor }
    Stream.Read(lsw,SizeOf(lsw));
    Stream.Read(lsh,SizeOf(lsh));

    Stream.Read(pf,SizeOf(pf));
    { Skip two bytes }
    Stream.Seek(2,soFromCurrent);

    { Is it followed by a Global Color table? }
    if (pf and $80)<>0 then
      begin
        { Skip the Global Color Table }
        GCTsize:=(pf and $07);
        GCTsize:=3*(2 shl GCTSize);
        Stream.Seek(GCTsize,soFromCurrent);
      end;

    { Go through the markers in the header. Stop when height and width are
      determined or when end of header is reached }

    while true do
      begin
        { Get the next marker }
        Stream.Read(c,SizeOf(c));

        if (c=$21) then
          begin
            { This is an Extension. }
            Stream.Seek(1,soFromCurrent);

            { Read the remainder of this Extension Block and while we're at it,
              read all possible Data Sub-blocks as well. }
            Stream.Read(BlkSize,1);
            while BlkSize<>0 do
             begin
               Stream.Seek(BlkSize,soFromCurrent);
               Stream.Read(BlkSize,1);
             end;
          end
        else if (c=$2c) then
          begin
            { This is the most holy of all... The Image Descriptor. }
            Stream.Read(lp,SizeOf(lp));
            Stream.Read(tp,SizeOf(tp));
            Stream.Read(h,SizeOf(w));
            Stream.Read(w,SizeOf(h));
            FWidthPixel:=w;
            FHeightPixel:=h;
            Stream.Seek(1,soFromCurrent);
            break;
          end
        else break;
      end; (* of while True *)
  end;

Wesentlich sind die Zuweisungen an FGIFType,FSizeBytes, FWidthPixel und FHeightPixel. Das sind Felder der Klasse und werden zur Ausgabe der Textinformationen benutzt.

Der Rest ist Magie und wird bei www.wotsit.org erklärt.

Der spezifische Code oben hat eine längere Geschichte: Er stammt aus dem Perl-Modul cgi.pm, ich habe ihn nach TP7 "portiert" (abgeschrieben) und inzwischen ist es Object Pascal. Ich habe also nie verstanden wie das GIF-Format aufgebaut ist, und was da genau gelesen wird. Rückfragen sind daher zwecklos :-).

8) GetDescription

Diese Methode erzeugt aus den in AnalyseData gesammelten Daten eine kurze Textzusammenfassung als Ausgabe des Programms.

  function TFileTypeGIF.GetDescription:string;
  begin
    Result:=DisplayName+#13;
    case FGIFType of
      gt87a:Result:=Result+'GIF87a'#13;
      gt89a:Result:=Result+'GIF89a'#13;
      else  Assert(False,Format('Unsupported FGIFType-Value (%d)',[ord(FGIFType)]));
    end;
    Result:=Result+
            Format('%d x %d Pixel',[FWidthPixel,FHeightPixel])+#13+
            Format('%d KB',[trunc(FSizeBytes/1024)]);
  end;

9) Das Format registrieren

Um das Format in die globale Liste der verfügbaren Formate aufzunehmen fügen wir noch einen Initialization-Block hinzu:

  initialization
    RegisterFormat(TFileTypeGIF);
  end.

Damit ist die Formatunterstützung abgeschlossen und es bleiben nur noch zwei Schritte:

10) Die Unit zum Projekt hinzufügen

Fügen Sie die neue Unit zum Projekt FileType hinzu:

  program FileType;
  uses
    Forms,
    FileTypeMain in 'FileTypeMain.pas' {fmFileType},
    {..}
    FileTypeGIF in 'FileTypeGIF.pas',
    FileTypeJPEG in 'FileTypeJPEG.pas';

  {..}

Jetzt kann man das neue Format in FileType testen und wenn alles zur Zufriedenheit funktioniert

11) Die Format-Unit dem Projekt zur Verfügung stellen

Schicken Sie die neue Unit und anderen evtl. nötigen Code per E-Mail an den Autor um sie in die komplette Distribution aufzunehmen.

 

Zurück zur FileType-Hauptseite


E-Mail:    marian@marian-aldenhoevel.de