Bereits mit dem Basiswissen über die Struktur von Zip-Files läßt sich ein voll funktionsfähiger Zip-Viewer entwickeln, der in eigene Programme integriert oder als eigenständiges kleines Tool gehandhabt werden kann. Dabei kommen zwei grundverschiedene Algorithmen in Frage.
Entweder durchsuchen Sie das ganze Zip-Archiv von Beginn an nach lokalen Dateiheadern so lange, bis der erste zentrale Dateiheader erscheint oder Sie durchsuchen nur die zentralen Dateiheader. Beide Vorgehensweisen führen Letztendes zum gleichen Ergebnis. Welche die schnellere ist, hängt vom Umfang des Archivs ab.
Im Folgenden wird die Variante mit Informationsgewinnung aus dem zentralen Dateiverzeichnis des Zip-Archivs beschrieben. Dieses befindet sich irgendwo gegen Ende der Zip-Datei nach der letzten komprimiert enthaltenen Inhaltsdatei. Wo genau, erfahren Sie aus dem Feld CentralOffSet aus dem Abschlußblock des Archivs (vgl. Tabelle 3 der Zip-Struktur).
Der Abschlußblock beginnt mindestens 22 Byte vor Archivende. Ein eventuell gespeicherter Kommentar verlagert den Beginn weiter nach vorn. Das Listing zeigt, wie Sie das Archiv vom Ende her nach dem Abschlußblock durchsuchen. Dabei werden höchstens 1 KByte Daten vom Ende her analysiert, ehe bei Mißerfolg abgebrochen wird.
type DirRecord = packed record // Ergebnis-Datensatz FileName : String; // Dateiname im Archiv PathName : String; // Pfadname DateTime : Integer; // (4) Datum und Uhrzeit CompresSize : Cardinal; // (4) gepackte Größe UncompresSize : Cardinal; // (4) ungepackte Größe end; type TZipList = array of DirRecord; // Rückgabe-Array const LocalFileHeaderSig = $04034B50; // 'PK34' CentralFileHeaderSig = $02014B50; // 'PK12' EndCentralDirSig = $06054B50; // 'PK56' ExtLocalSig = $08074B50; // 'PK78' function FillZipList(ZipName: String; var ZipList: TZipList): Integer; // Ermittelt die Kopfdaten aller im ZIP-Archiv gespeicherten Dateien var ZipStream: TFileStream; ZipRecord: ZipDirEntry; EndRecord: ZipEndRecord; cBuf: array[0..255]of Char; nPos: LongInt; nSign, idx: Integer; nFlag: Byte; bEndFound: Boolean; begin SetLength(ZipList,0); // dynam. Array leeren if not FileExists(ZipName) then Exit; ZipStream:=TFileStream.Create(ZipName,fmOpenRead); // Archiv öffnen try if ZipStream.Size>22 then begin // EndeBlock ist 22 lang bEndFound:=false; nPos:=ZipStream.Seek(-20,soFromEnd); while (not bEndFound) and (nPos>0) and (nPos+1024>ZipStream.Size) do begin nPos:=ZipStream.Seek(-2,soFromCurrent); // 2 zurück ZipStream.Read(nFlag,1); // 1 vor if nFlag=Ord('P') then begin // in 1er-Schritten suchen nPos:=ZipStream.Seek(-1,soFromCurrent); // vor das 'P' zurück ZipStream.Read(nSign,4); if nSign=EndCentralDirSig then bEndFound:=true // Header-Flag gefunden else ZipStream.Seek(-4,soFromCurrent); end; end; // durchsucht oder gefund |
Ist die Kennung des EndCentralDirSig gefunden, wird der End-Record ausgelesen und das Datenfeld CentralOffSet zur Verschiebung des Streamzeigers auf den Ersteintrag im Zentralverzeichnis benutzt.
if bEndFound then begin ZipStream.Read(EndRecord,SizeOf(EndRecord)); // 18-Byte-Struktur lesen ZipStream.Seek(EndRecord.CentralOffSet,soFromBeginning); |
Ist dieser korrekt erreicht wird geprüft, ob tatsächlich auf einen zentralen Dateiheader positioniert wurde. Im Weiteren lesen Sie den folgenden Block aus 42 fixen Byte in einen Record ZipRecord der eigens deklarierten Struktur ZipDirEntry ein.
idx:=0; // Anfang des Zentralheaders erreicht repeat ZipStream.Read(nSign,4); // Header-Flag lesen if nSign=CentralFileHeaderSig then begin // zentr Dat-Header gefund ZipStream.Read(ZipRecord,SizeOf(ZipRecord)); // 42-Byte-Struktur |
Der variabel lange Dateiname wird entsprechend seiner im Verzeichnisdatensatz enthaltenen Länge in einen separaten Puffer cBuf eingelesen und null-terminiert. Der Dateiname liegt danach leider OEM-codiert und nicht im Windows-typischen ANSI-Zeichensatz vor, wobei als Pfad-Trennzeichen des Unix-typische "/" statt des vertrauten "\" verwendet wird. Die Konvertierung und nachfolgende Aufspaltung in Pfad- und Dateinamen gestaltet sich aber unter Delphi mit den Funktionen OemToChar, ExtractFileName und ExtractFilePath unkompliziert.
ZipStream.Read(cBuf,ZipRecord.FileNameLen); // Dateinamen lesen cBuf[ZipRecord.FileNameLen]:=#0; // nullterminieren OemToChar(cBuf,cBuf); // OEM --> ANSI while StrScan(cBuf,'/')<>nil do StrScan(cBuf,'/')^:='\'; ZipStream.Seek(ZipRecord.FileCommentLen,soFromCurrent); |
Anhand der Record-Daten werden nun die Dateiangaben zu Datum und Uhrzeit, komprimierter und unkomprimierter Größe der Originaldatei erfaßt. Gesammelt werden die Angaben zu den Inhaltsdateien eines solchen Zip-Archives im abgebildeten Listing in einem dynamischen Array.
SetLength(ZipList,idx+1); // dynam. Array verlängern ZipList[idx].DateTime:=ZipRecord.DateTime; // Ergebnisse einspeichern ZipList[idx].CompresSize:=ZipRecord.CompresSize; ZipList[idx].UncompresSize:=ZipRecord.UncompresSize; ZipList[idx].FileName:=ExtractFileName(cBuf); ZipList[idx].PathName:=ExtractFilePath(cBuf); Inc(idx); end; until nSign<>CentralFileHeaderSig; // bis ungültiges Flag if ZipStream.Position=nPos+4 then FillZipList:=ZIP_NO_ERR; end; // if bEndFound end; // try finally ZipStream.Free; end; end; |
Sollte Ihre Delphi-Version (vor Delphi 4) den Datentyp "dynamischse Array" noch nicht unterstützen, verwenden Sie statt dessen einfach eine Liste vom Typ TList zum Sammeln der Angaben über alle im Archiv enthaltenen Dateien. Das Download-Archiv "ZipView3.dpr" im Anhang beinhaltet die angepaßte Variante für die frühen Delphi-Versionen.
Der Einbau dieser Kernroutine FillZipList aus meiner Unit "ZipList.pas" ist bereits der halbe Zip-Viewer.Das Übertragen der Daten aus der Sammelliste in ein Gitterelement TStringGrid einschließlich Formatierung ist im folgenden Listing angedeutet. Bei der Formatierung haben Sie natürlich die Möglichkeit, andere Wege zu gehen. Eventuell errechnen Sie auch noch in einer zusätzlichen Gitterspalte die Komprimierungsrate der Dateien als Prozentzahl. Diese Angabe ist nämlich im Zip-Archiv selbst nicht mit gespeichert.
procedure TFrmMain.BtnViewClick(Sender: TObject); var DirList: TZipList; // dynamischs Array of DirRecord i: Integer; begin Caption:='ZipViewer'; if FillZipList(EdName.Text,DirList)=ZIP_NO_ERR then begin // erfolgreich ZipGrid.RowCount:=Length(DirList)+1; // Gitter verlängern for i:=0 to High(DirList) do begin // bis zum letzten Arrayelement ZipGrid.Cells[0,i+1]:=DirList[i].FileName; ZipGrid.Cells[1,i+1]:= FormatDateTime('dd.mm.yy hh:nn',FileDateToDateTime(DirList[i].DateTime)); ZipGrid.Cells[2,i+1]:=FormatFloat('#00000000',DirList[i].UnCompresSize); ZipGrid.Cells[3,i+1]:=FormatFloat('#00000000',DirList[i].CompresSize); ZipGrid.Cells[4,i+1]:=DirList[i].PathName; end; ZipGrid.Cells[0,0]:='Dateinamen ('+IntToStr(Length(DirList))+')'; Caption:='ZipViewer [ '+EdName.Text+' ]'; end else begin // Archiv nicht erfolgreich durchforstet ZipGrid.RowCount:=2; // Gitter leeren for i:=0 to 4 do ZipGrid.Cells[i,1]:=''; end; DirList:=nil; // dynamisches Übergabearray freigeben end; |
Die Hauptarbeit im Zip-Viewer leistet die Funktion FillZipList aus dem ersten Listing.
Den fertigen Zip-Viewer als eigenständiges Programm sehen Sie im folgenden Bild in Aktion. Interessenten finden im Quelltextarchiv des Programmes auch einen Abschnitt zur Verknüpfung des Dateityps "ZIP" mit dem Viewer über die Windows-Registry. Damit steht Ihnen der eigene Zip-Viewer dann auch im Kontextmenü des Windows-Explorers zur Verfügung.
Der Viewer kann übrigens nicht nur echte Zip-Dateien, sondern wie abgebildet auch selbstextrahierende EXE-Datein (SFX-Archive) einsehen. Wenn Sie noch mehr wollen, nämlich Archive auch selbst packen und entpacken, dann sehen Sie auf die Seite Zip-Programme
J. Hummel, Juni 2000
[ Programmierung ] [ Zip-Programmierung ]