HOME - - - - - Lazarus Tutorials TOC - - - - - - Other material for programmers
Delicious.Com Bookmark this on Delicious     StumbleUpon.Com Recommend to StumbleUpon

Access to datafiles

The AssignFile / Readln alternative

Along With...

Using User Defined Records for easy parameter passing

The code that comes from this is far from "perfect". Also far from "complete"... but it DOES work, and it illustrates a few things.

It reads a text file "the old fashioned way", a line at a time. This DOES have its uses!

As the code supplied with this tutorial stands, you can read a few lines from a file, without having to read it all. With a small file, the difference is unimportant. With larger files it becomes important, in some uses of the file.

With slight extensions from what appears here, it is possible to read a file from the end towards the beginning. Or add things to a file, without first reading the rest of the file into memory.

It is incomplete in that it doesn't respond well if, for instance, you try to look at the contents of a file when the file isn't present on the storage you are accessing. There are other similar flaws, such as needing code added to call CloseFile, to do things "properly"... but I suspect that all necessary "closing" happens anyway, when the app closes. Better to do it explicitly, though.

But it is a good start, one that can be taken further, can deal with all the "stuff" which ought to be dealt with. Like, for instance, reacting graciously if asked to fetch lines from a file that isn't present on the disk.

(The data files this can read, by the way, are files created by simple text editors, e.g. Notepad (free with Windows), or the excellent and affordable Textpad... with which I've written my web pages for as long as I can remember, certainly for more than 15 years.

More good news: the complete sourcecode, with all relevant support files is provided for you in a .zip archive which will download from my server if you click the link.

Furthermore, it shows you how certain elements of the task can be "wrapped" in subroutines.

AND it uses user defined types to make the code "tidier", more elegant.

So... NOT good as an introduction to the old "assignfile"/ "readln" method of accessing a file... but still a tutorial with merit, I hope you will find.

I am indebted to the Good Stuff at http://wiki.freepascal.org/File_Handling_In_Pascal for the help I had from there.

Some months after I wrote the pages you are reading, I wrote another tutorial in which I touch on creating and using what are essentially custom data-types, the user-defined records. That tutorial, also, addressed two topics. What it says about using sub-units in your program is directly applicable to things presented here. That newer tutorial is "Using "Units" in Lazarus (or Delphi) programming".

Not only does this tutorial repeat, re-work information about using user-defined records, but it also has considerable overlap with another tutorial about FindFirst/ FindNext. That alternative tutorial is "Using "FindFirst" and "FindNext" in Lazarus programming".

But....

There is, alas, an annoying flaw in the code so far.

It WORKS! Don't worry.

Reading files like this involves three things...

That's "all". In the code below, I've done the second step with a nice, elegant (well, more elegant than some alternatives) sub-routine which "hides" most of the messy detail. Once you have done AssignFile, you can call udrReadLineOfData as often as you like. Each call returns a string holding the next line of the file, and an error code. If that code is zero, there was no error, the material in the string should be something from the file. The "udr" at the start of the function's name, to signify "User Defined Resource", because that is the value it returns is of a user defined record type.

So far, I have not been able to "wrap" the code for the first step into a subroutine. It ALMOST works... beware of not noticing that the nearly working system I have, present in the code in remmed- out statements, is not working. If you look carefully, the first line reads okay, as do, I think, the 3rd, 4th, and following lines. But when the second line is read and passed to the memo for inspection, a little corruption of the output arises. The output SHOULD look like...

7 >>lt12k20a03000000019<<
6 >>ws12k20a03000339116<<
5 >>rf12k20a01000000842<<
4 >>cmOnOff1 is alarm status, OnOff2 is doors<<
3 >>cmSnipped previously: 2012-07-23<<
2 >>cmSnipped 2012-12-05... had to take out some nulls at atart of ss12l05n15 line<<
1 >>cmStartedx with material snipped by hand from end of previous.<<
---
This memo is to let us see
the data which we'read from the file.
---
The reading of the file, and displaying of
the data is stopped rather crudely in this demo program.

... in other words you should see the first seven lines of whatever is in LDN194datafile.txt. (Although that looks like gibberish, perhaps, it is a genuine data file from my FarWatch app, which monitors temperatures and other parameters, save the data, plots a graph, and makes the graph visible in near real time across the internet.)

At the moment, the offending code only takes two lines, and do the job. But that code does not take care of all the Things That Could Happen... like the file you said you wanted to look at not existing on the disk. Nor can you change the file you want to look at without changing the program's coding. By the time you add all the basic features of a "proper" "set up the connection to the file", it will take rather more than two lines!

The code I've included in remmed out lines "should" have worked. It's probably something simple, silly. (If you spot it, I would be VERY pleased to hear from you!!)

The User Defined Record type

Using the provisions in most Pascals, including Delphi and Lazarus, to define a custom "type" to hold a record designed to meet your needs can be very helpful when you are dealing with subroutines which need to pass several related values back and forth.

The user defined record lets you "package up" more than one "variable" in a "single" "thing". I've used user defined records in the code below. I've done a general introduction in my tutorial Introducing User Defined Records. (That also introduces using multiple units.)

unit ldn194u1;

{$mode objfpc}{$H+}

//Demo of accessing a file with AssignFile and ReadLn, using
//  subroutines and user defined records to make the code "neater",
//  and "elegant".

//Needs to have code for calling CloseFile added, to do things
//  "properly"... but I suspect that all necessary "closing" happens
//  anyway, when the app closes. But better to do it explicitly.

//Needs to have code added to send back error reports, via the .wErr
//   element in the TDataFileOpenResult or TDataFileRecord records,
//   as relevant.

interface

uses
  Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs,
  StdCtrls, ExtCtrls, LDN194readingsu1;

const
vers='05 Feb 16';
  //(Notes to myself....
  //Started 03 Feb 16, when I should have been doing taxes, etc, etc.
  //Further work one morning when I was going to BE matinee, and was
  //   still in Chi.

type TDataFileOpenResult = record //no ; here
  dfHandle:textfile;
  wErr:word;
  end;//of declaration of user-defined-record type: TDataFileOpenResult


type TDataFileRecord = record //no ; here
  sRec:string;
  wErr:word;
  dfHandle:textfile;//Must be passed back and forth,
     //with calls of readln, etc. because contents change.
     //The "handle" is a thing which keeps track
     //of place in file, if nothing else.
end;//of declaration of user-defined-record type: TDataFileRecord

type
   { TLDN194f1 }

  TLDN194f1 = class(TForm)
    laCyclesCount: TLabel;
    laTxtCycles: TLabel;
    tiMainCycleClock: TTimer;
    procedure FormActivate(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure tiMainCycleClockTimer(Sender: TObject);

  private
    {private declarations }
  public
    {public declarations }
    liTmp,liCyclesCount:longint;
    sDtLgPath,sDtLgName:string;//Path to include "/" at right hand end,
        //if not null, indication "use app folder"
    udrDataFileOpenResultTmp,udrDataFileOpenResultDtLg:TDataFileOpenResult;
    dfDtLg,dfKludge,dfErrLg:textfile;
    procedure DoRead;
    function udrOpenOrCreateDataFile(sPath,sName:string):TDataFileOpenResult;
    function udrReadLineOfData(var dfTmpL:textfile):TDataFileRecord;
       //Handle comes IN via parameter, is passed BACK as part
       //   of resulting record.
       //Returns 0 in .wErr if no problem detected... but at 05 Feb 16
       //   doesn't LOOK for errors, and will ALWAYS return zero.

  end;

var
  LDN194f1: TLDN194f1;

implementation

{$R *.lfm}
{$R+}

{ TLDN194f1 }

procedure TLDN194f1.FormCreate(Sender: TObject);
//var udrTmpL:TudrDataFileOpenResult;
begin
  sDtLgPath:='';//Path to include "/" at right hand end,
  sDtLgName:='LDN194datafile.txt';

  //(* THIS WORKS.. BUT NOT ELEGANT!!
  assignfile(dfDtLg,sDtLgPath+sDtLgName);
  reset(dfDtLg);
  //(end of "THIS WORKS" *)

  (*doesn't work, but "SHOULD"!!... (TKB: see also DS052) 05 Feb 16
  udrDataFileOpenResultTmp:=udrOpenOrCreateDataFile(sDtLgPath,sDtLgName);//Do this and next with one routine in due course.
  if true{no error} then begin
      dfDtLg:=udrDataFileOpenResultTmp.dfHandle;
      end//no ; here
    else begin
      {report problem};
    end;
  //End of "Doesn't (work), but should"
  *)
end;

procedure TLDN194f1.tiMainCycleClockTimer(Sender: TObject);
begin
  inc(liCyclesCount);
  laCyclesCount.caption:=inttostr(liCyclesCount);

  if liCyclesCount<8 then DoRead;

end;

procedure TLDN194f1.FormActivate(Sender: TObject);
begin
  liCyclesCount:=0;
  LDN194ReadingsF1.meTmpDisplayFileRead.lines.clear;
  LDN194ReadingsF1.meTmpDisplayFileRead.
           lines.insert(0,'the data is stopped rather crudely in this demo program.');
  LDN194ReadingsF1.meTmpDisplayFileRead.
           lines.insert(0,'The reading of the file, and displaying of');
  LDN194ReadingsF1.meTmpDisplayFileRead.
           lines.insert(0,'---');
  LDN194ReadingsF1.meTmpDisplayFileRead.
           lines.insert(0,'the data which we''read from the file.');
  LDN194ReadingsF1.meTmpDisplayFileRead.
           lines.insert(0,'This memo is to let us see');
  LDN194ReadingsF1.meTmpDisplayFileRead.
           lines.insert(0,'---');

end;

procedure TLDN194f1.DoRead;
var udrFrmFileL:TDataFileRecord;
begin

  udrFrmFileL:=udrReadLineOfData(dfDtLg);
     //Using dfDtLg in the above is NOT a problem, kludge, etc.
     //Returns 0 in .wErr if no problem detected... but at 05 Feb 16
     //  it doesn't LOOK for errors, and will ALWAYS return zero.

  dfDtLg:=udrFrmFileL.dfHandle;//Need to update what is in the
     //global dfDtLg after calling udrReadLineOfData. Often this
     //could be done by using a VAR parameter. Not sure how to
     //do that more elegantly when the parameter is a user
     //defined record. (The data in the handle to that file changed
     //inside the ReadLineOfData function.

if udrFrmFileL.wErr=0 {i.e. No error during function} then begin
   LDN194ReadingsF1.meTmpDisplayFileRead.
         lines.insert(0,udrFrmFileL.sRec);
   end//no ; here
  else begin
   //Run error 103 may be "File not open"
   //Need to make this and udrReadLineOfData fancier, make the
      //latter report WHICH error encountered, and the former
      //deal with all that can be anticipated.
   LDN194ReadingsF1.meTmpDisplayFileRead.
         lines.insert(0,'Function "udrReadLineOfData"' +
                  'encountered problem');
   end;//else

end;//DoRead

function TLDN194f1.udrOpenOrCreateDataFile(sPath,sName:string):TDataFileOpenResult;
//At 05 Feb 16, this was not QUITE working. Sigh.
//SEEMED to work, unless you looked closely...
//   Subsequent readln's held bad data.
var
  udtDFOR_tmpL:TDataFileOpenResult;
  dfTmpL:textfile;

begin
  udtDFOR_tmpL.wErr:=0;//Assume for now no error seen.
  assignfile(dfTmpL,sPath+sName);
  reset(dfTmpL);
  //Needs error checks added, which should make udtDFOR_tmpL.wErr non-zero.
  udtDFOR_tmpL.dfHandle:=dfTmpL;
  result:=udtDFOR_tmpL;
end;//udrOpenOrCreateDataFileOld

function TLDN194f1.udrReadLineOfData(var dfTmpL:textfile):TDataFileRecord;
//Handle comes IN via parameter, is passed BACK as part of resulting record.
//This becomes really nice to have when your app is dealing with
//    more than one datafile.
var udrTmpL:TDataFileRecord;

begin
        udrTmpL.wErr:=0;//Init with "no error seen"
        try
          readln(dfTmpL,udrTmpL.sRec);
        except on E: EInOutError do begin
          udrTmpL.wErr:=999;
          end;//except
        end;//try.. except
        udrTmpL.sRec:=inttostr(liCyclesCount)+' >>'+udrTmpL.sRec+'<<';
           //The ">>", "<<" added to show up anything
           //  that looks like a space, at start or end
           //  of string fetched.
           //The direct reference to the global variable
           //  liCyclesCount is Bad Programming... but it is
           //  here JUST to number the lines, in the output
           //  and the mis-use is not so very aggregious, I think.
        udrTmpL.dfHandle:=dfTmpL;
      result:=udrTmpL;
    end;//udrReadLineOfData

end.


Useful, I hope?

Well... worth at least what I was paid for writing it. I hope you found bits useful.





Search across all my sites with the Google search...

Custom Search
            powered by FreeFind
  Site search Web search
Site Map    What's New    Search This search merely looks for the words you enter. It won't answer "Where can I download InpOut32?"
Ad from page's editor: Yes.. I do enjoy compiling these things for you. I hope they are helpful. However... this doesn't pay my bills!!! Sheepdog Software (tm) is supposed to help do that, so if you found this stuff useful, (and you run a Windows or MS-DOS PC) please visit my freeware and shareware page, download something, and circulate it for me? Links on your page to this page would also be appreciated!
Click here to visit editor's freeware, shareware page.

Link to Lazarus Tutorials main page
How to contact the editor of this page, Tom Boyd


Please consider contributing to the author of this site... and if you don't want to do that, at least check out his introduction to the new micro-donations system Flattr.htm....



Valid HTML 4.01 Transitional Page WILL BE tested for compliance with INDUSTRY (not MS-only) standards, using the free, publicly accessible validator at validator.w3.org. Mostly passes. There were two "unknown attributes" in Google+ button code. Sigh.


If this page causes a script to run, why? Because of things like Google panels, and the code for the search button. Why do I mention scripts? Be sure you know all you need to about spyware.

....... P a g e . . . E n d s .....