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:
Voraussetzungen für die Implementation eines neuen Dateiformats für FileType sind
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 programAuthor: 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.
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
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.
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.
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).
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
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 :-).
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;
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:
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
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