Čtení unixových souborů - Builder.cz - Informacni server o programovani

Odběr fotomagazínu

Fotografický magazín "iZIN IDIF" každý týden ve Vašem e-mailu.
Co nového ve světě fotografie!

 

Zadejte Vaši e-mailovou adresu:

Kamarád fotí rád?

Přihlas ho k odběru fotomagazínu!

 

Zadejte e-mailovou adresu kamaráda:



Delphi

Čtení unixových souborů

serial

23. ledna 2002, 00.00 | S rozmachem Linuxu a novou podporou Delphi resp. Kylixu v Linuxu se nám čím dál tím více začínají objevovat textové soubory s unixovým řádkováním. Pojďme se tedy podívat se dají v Delphi číst...

Již jste se pravděpodobně setkali s textovými soubory ve formátu Unixu. Ze začátku, když se začaly objevovat s nimi bylo dost problémů, protože velmi málo editorů s nimi umělo pracovat správně. Dnes je však zcela jiná doba. A proto si ukažme, jak rozšířit i vlastní programy, aby se už ani ony nenechali zaskočit o trochu odlišným formátováním / zalomením řádek.

Zcela zde budu ignorovat kódování souborů, osobně si myslím, že je to zcela jiná kapitola.

Unixové soubory se mnohem více začali objevovat i v systémech Microsoft® s postupným rozšiřováním Javy. V současné době pro nás, to bude fakt, že Delphi se posunuly i na platformu Linuxu. A dodám-li, že Kylix se stal nejoblíbenějším vývojovým prostředkem pro Linux v minulém roce a neustálým růstem počtu uživatelů Linuxu, pak si každý, dříve či později, se setká s nutností umět správně číst unixový soubor.

Neřeknu nic nového, když řeknu, že pod Microsoft® Windows® se řádky v textových souborech oddělují CR LF (Carriage Return, Line Feed), neboli systémová 13 a 10. Zatímco v systémech vzniklých z Unixu se řádky oddělují jen LF, systémová 10. Díky tomu u FTP, kde se tato záměna prováděla automaticky, musela vzniknout korekce pro datové soubory v podobě příkazu binary.

Co se týče standardního přístupu v Delphi takovýmto textovým souborům, tak zatím jen verze 6 se s nimi umí vypořádat přímo přes readln. Což ovšem znamená, že uživatelé nižších verzí, než je Delphi 6, se s tím musí vypořádat sami. A právě pro ně je tento článek.

Základní způsob, který přímo využívá funkci readln, jste asi používali pro textové soubory doposud. Proto se nejdříve podíváme, jak ho elegantně upravit – s co nejméně změnami.

// soubor cutting.dpr
program cutting;

{$APPTYPE CONSOLE}

uses SysUtils;

var F: TextFile;
     mln, ln: string;
begin
     AssignFile(F, 'example.txt');
     Reset(F);
     while not eof(F) do begin
          Readln(F, mln);
// cutting
          repeat
               ln:= copy(mln, 1, pos(#10, mln) -1);
               delete(mln, 1, length(ln) +1);

// do something with ln
               writeln(ln);

          until length(mln) = 0;
     end;
     CloseFile(F);
end.

V podstatě zde není nic jiného než přidaný repeat, který načtený řetezec rozděluje na pravé řádky. Je si ale nutno uvědomit to, že při volání readln se načte do definované proměnné celý obsah souboru! V souboru, se totiž nikde nevyskytuje CR LF. Takže načtení může být u rozsáhlých souborů mnohem pomalejší. Celý postup má tu výhodu, že algoritmus, který je uvnitř nepozná, zda-li právě čte klasický textový soubor či textový soubor z Unixu.

Jiným způsobem, který je mimochodem asi mnohem efektivnější a dalo by se říci i jednodušším na implementaci, je definovat si vlastní AssignFile v našem případě AssignStreamFile. Kód není pro laika zcela jednoduchý, už jen proto, že se zde používají streamy. Hlubší prostudování nechám na vás. Jen bych upozornil na část, kde se definuje funkce StreamFileInOut a do ni je krásně vloženo několik procedur. Tento postup není v Delphi, až tak moc rozšířený. Ale o to je v některých situacích účinnější.

Jen bych rád dodal, že s tímto kód již nebudete muset ve svém programu upravovat. Takže ho může používat i úplný začátečník, který nemá ani potuchy, co se to tam vlastně děje.

// soubor StreamFile.pas
unit StreamFile;

interface
uses SysUtils;

procedure AssignStreamFile(var F: Text; const Filename: String);
implementation

const BufferSize = 128;
type TStreamBuffer = array [1..High(Integer)] of Char;
     TStreamBufferPointer = ^TStreamBuffer;
     TStreamFilerecord = record
     case Integer of
          1: (
               Filehandle: Integer;
               Buffer: TStreamBufferPointer;
               Bufferoffset: Integer;
               ReadCount: Integer;
           );
           2: (
               Dummy: array [1 .. 32] of Char
           )
     end;

function StreamFileOpen(var F: TTextRec): Integer;
begin
     with TStreamFilerecord(F.UserData) do begin
          GetMem(Buffer, BufferSize);
          case F.Mode of
               fmInput: FileHandle:= FileOpen(StrPas(F.Name), fmShareDenyNone);
               fmOutput: FileHandle:= FileCreate(StrPas(F.Name));
               fmInOut: begin
                     FileHandle:= FileOpen(StrPas(F.Name), fmShareDenyNone or fmOpenWrite or fmOpenRead);
                     if FileHandle <> -1 then FileSeek(FileHandle, 0, 2); // Move to end of file.
                     F.Mode:= fmOutput;
                end;
          end;
          Bufferoffset:= 0;
          ReadCount:= 0;
          F.bufend := 0; // if this is not here it thinks we are at eof.
          if FileHandle = -1 then Result:= -1
               else Result:= 0;
     end;
end; // StreamFileOpen

function StreamFileInOut(var F: TTextRec): Integer;
     procedure Read(var Data: TStreamFilerecord);
          procedure CopyData;
          begin
               while (F.bufend < Sizeof(F.Buffer) - 2) and (Data.Bufferoffset <= Data.ReadCount) and (Data.Buffer [Data.Bufferoffset] <> #10) do begin
                    F.Buffer [F.bufend ]:= Data.Buffer^ [Data.Bufferoffset];
                    Inc(Data.Bufferoffset);
                    Inc(F.bufend );
               end;
               if Data.Buffer [Data.Bufferoffset] = #10 then begin
                    F.Buffer [F.bufend ]:= #13;
                    Inc(F.bufend );
                    F.Buffer [F.bufend ]:= #10;
                    Inc(F.bufend );
                    Inc(Data.Bufferoffset);
               end;
          end; // copyData

     begin // begin procedure Read
          F.bufend := 0;
          F.BufPos:= 0;
          F.Buffer:= '';
          repeat
               if (Data.ReadCount = 0) or (Data.Bufferoffset > Data.ReadCount) then begin
                    Data.Bufferoffset:= 1;
                    Data.ReadCount:= FileRead(Data.FileHandle, Data.Buffer^, BufferSize);
               end;
               CopyData;
          until (Data.ReadCount = 0) or (F.bufend >= Sizeof(F.Buffer) - 2);
          Result:= 0;
     end; // end procedure read

     procedure Write(var Data: TStreamFilerecord);
     var Destination: Integer;
     II: Integer;
     begin
          with TStreamFilerecord(F.UserData) do begin
               Destination:= 0;
               for  II:= 0 to F.BufPos - 1 do
                    if F.Buffer [II] <> #13 then begin
     Inc(Destination);
     Buffer^[Destination]:= F.Buffer [II];
                    end;
               FileWrite(FileHandle, Buffer^, Destination);
               F.BufPos:= 0;
               Result:= 0;
          end;
     end; // write

begin // beginw StreamFileInOut
     case F.Mode of
          fmInput: Read(TStreamFilerecord(F.UserData));
          fmOutput: Write(TStreamFilerecord(F.UserData));
     end;
end; // end StreamFileInOut

function StreamFileFlush(var F: TTextRec): Integer;
begin
     Result:= 0;
end; // StreamFileFlush

function StreamFileClose(var F: TTextRec): Integer;
begin
     with TStreamFilerecord(F.UserData) do begin
          FreeMem(Buffer);
          FileClose(FileHandle);
     end;
     Result:= 0;
end; // StreamFileClose

procedure AssignStreamFile(var F: Text; const Filename: String);
begin
     with TTextRec(F) do begin
          Mode:= fmClosed;
          BufPtr:= @Buffer;
          BufSize:= Sizeof(Buffer);
          OpenFunc:= @StreamFileOpen;
          InOutFunc:= @StreamFileInOut;
          FlushFunc:= @StreamFileFlush;
          CloseFunc:= @StreamFileClose;
          StrPLCopy(Name, FileName, Sizeof(Name) -1);
     end;
end; // AssignStreamFile

end.

Pro jednoduchost v programu stačí zavolat AssignStreamFile místo AssignFile. Takto otevřený soubor opět nerozlišuje, čte-li se normální windowsový či unixový soubor.

Celý tento postup má tu výhodu, že ve zdrojovém kódu, stačí jen nahradit ono AssignFile za AssignStreamFile. A dále vložit do uses StreamFile. A nic víc. Jednoduchý program by mohl vypadat takto:

// soubor ReadFile.dpr
program ReadFile;

{$APPTYPE CONSOLE}

uses SysUtils, StreamFile; // změna

var F: TextFile;
     S: String;
begin
     AssignStreamFile(F, 'example.txt'); // změna
     Reset(F);
     Readln(F, S);
     while not Eof(F) do begin
          Read(F, S);
          Writeln(S);
     end;
     CloseFile(F);
end.

A to je pro dnešek vše. Děkuji vám za pozornost. A pokud máte zájem o zdrojové kódy zde uváděné, pak sáhněte po tomto souboru unixfile.zip (2 kB).

Tématické zařazení:

 » Rubriky  » Delphi  

 » Rubriky  » Windows  

 

 

 

Nejčtenější články
Nejlépe hodnocené články

 

Přihlášení k mému účtu

Uživatelské jméno:

Heslo: