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).
StreamIsThisFormatHier 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
AnalyseDataDies 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 :-).
GetDescriptionDiese 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