Lesson 11. File Handling.
When something is stored on the disk, it is stored into a file. Every component in the disk is organised into files. Therefore, if we wish to know how to store our data permanently, we need to learn and understand how to access disk files. Tapes and other storage media is another problem but is out of this syllabus.
Files can be used to store data, programs, or even configuration details for a particular program. In this lesson we shall explore the uses of data files in particular. Data files are used to store data and the program which maintains a data file generally has to insert, append or retrieve and update certain items in the file.
There are mainly two types of file organisation in Pascal:
File handling procedures and functions.
Here is a list of procedures and functions which are to be used on files. Some only apply to binary files while some others may apply only to text files.
ASSIGN
- procedure. Syntax ASSIGN( filevar, strfilename ).File types - TEXT and BINARY.
Associates the logical(filevar) and physical(OS name) files. Pascal can only use logical filevars when accessing files. DOS filenames cannot be used by Pascal.
APPEND - procedure. Syntax APPEND( filevar ).
File types - TEXT only.
Opens a TEXT file and sets the file pointer to the end of file. Any writing done to the file will therefore be done at the end of the file.
RESET - procedure. Syntax RESET( filevar ).
File types - TEXT and BINARY.
This procedure is used to open text and binary files. Text files are opened for reading only. Binary files provide random access no matter which open mode is done.
REWRITE - procedure. Syntax REWRITE( filevar ).
File types - TEXT and BINARY.
This procedure opens a new file. REWRITE will always create a new file. Use this procedure carefully or all data may be destroyed. Text files are opened for writing only.
EOF - function. Syntax EOF( filevar ).
File types - TEXT and BINARY.
This function returns TRUE when the end of file marker is found, otherwise it returns FALSE.
FILESIZE - function. Syntax FILESIZE( filevar ).
File types - BINARY only.
Returns the number of records in a file. Works only on BINARY files.
FILEPOS - function. Syntax FILEPOS( filevar ).
File types - BINARY only.
Returns the position of file pointer in a particular file. Works only on BINARY files.
SEEK - procedure. Syntax SEEK( filevar, position ).
File types - BINARY only.
Position or places the file pointer for the specified file at the specified position. Remember that position 0 is the first position in a file. Works on BINARY files only.
WRITE - procedure. Syntax WRITE( filevar, filerec ).
File types - BINARY and TEXT. Preferably BINARY.
Writes a record to a file. Record will be placed at the current position of the file pointer. After writing, the position of the file pointer advances one record.
READ - procedure Syntax READ( filevar, filerec ).
File types - BINARY and TEXT. Preferably BINARY.
Reads a record from a file. The record read will depend on the position of the file pointer. After reading, the position of the file pointer advances to the next record.
WRITELN - procedure. Syntax WRITELN( filevar,item-1,..item-n ).
File types - TEXT only.
Writes a line to a text file. After writing, the position of the file pointer advances one line. Works for TEXT files only.
READLN - procedure. Syntax READLN( filevar,item-1,..item-n ).
File types - TEXT only.
Reads a line from a text file. After reading, the file pointer will be advanced to the next line. Works for TEXT files only.
When reading to a file, care must be taken not to read a line or record which is not there. If the area of the file to be read is empty, the compiler will return an error message.
When writing to a file, you should try not to write items to the wrong location. Once written, the original data would be destroyed. There are no possible methods to recover this lost data unless a backup has been made.
It is best to use the READLN and WRITELN procedures to manipulate TEXT files. The READ and WRITE may also be used but considerable care must be taken by the programmer when an end of line marker is to be inserted or read.
A BINARY file can only use the procedures WRITE and READ. The compiler takes care of the size allocated on disk for a certain record. There are no end of line markers for a BINARY file.
Text files.
Here is a program which prints text files. You have an option to print to printer or screen.
{ This program reads a text file line by line and displays or
prints it.
}
program TextFiles;
uses CRT;
var
Source, Target : text;
Destination : char;
Filename1, Filename2 : string;
FileLine : string;
Lines : integer;
begin { main program }
ClrScr;
Write('Enter filename to display/print : ');
ReadLn( Filename1 );
Write('Destination : [P]rinter / [S]creen / New [F]ile ? ');
ReadLn( Destination );
Destination := upcase( Destination );
Assign( Source, Filename1 );
if Destination = 'P' then
Assign( Target, 'PRN' )
else if Destination = 'S' then
Assign( Target, 'CON' )
else if Destination = 'F' then
begin
Write('Enter Filename To Store : ');
ReadLn( Filename2 );
Assign( Target, Filename2 );
end;
{$I-} Reset( Source ); {$I+}
if IOResult = 0 then
begin
Rewrite( Target ); Lines := 0;
WriteLn('File printed : ', Filename1 );
while not eof ( Source ) do
begin
Inc( Lines );
ReadLn( Source, FileLine );
WriteLn( Target, Lines:4,') ',FileLine );
end;
Close( Source ); Close( Target );
end
else
WriteLn('File not opened. Error!');
Write('Press <ENTER>'); ReadLn;
end. { main program }
First, look at the declaration section of this program. The VARiables SOURCE and TARGET are declared as TEXT. This means they are used to reference to TEXT files. These are known as FILE VARIABLES
because they do not refer to memory cells but files. The rest of the declaration is pretty simple.
The program will accept a filename from the user and the ASSIGN procedure will assign (link) it to the file variable SOURCE. The filename accepted from the user should be a filename which is on the disk. If the file does not exist on the disk, the program will display an error message. You must include the extension when typing the filename. Pascal programs requires a file variable to be used whenever a reference to a file is made. The filename which is used by the operating system is not valid to be used in a Pascal program.
It will also ask the user which output device to use to print/display the file. If the user chooses 'P' the TARGET file variable will be assigned 'PRN' which is DOS' (Disk Operating System) standard printer file. Any output directed to the DOS file PRN will be output to the printer. Errors may occur if the printer is not working or off-line. If 'S' is chosen, the output will be directed to the screen. CON is the console filename for DOS. Output will be directed to screen because the console's standard output device is the screen.
After the program associates the physical files and the file variables (logical filenames), it will try to open the SOURCE file for reading (RESET is used). If there are no errors in opening the file, the program will proceed to open the TARGET file for writing (REWRITE is used) or else it will display an error message and the program will terminate.
If the files were open successfully, the program will start reading the SOURCE file, line by line, and sending each line read to the TARGET file. Please note the syntax when using WRITELN and READLN. You will see that the first parameters in both the procedures are file variables. If file variables are used as the first parameters, the output (WRITELN) or input (READLN) would not be from the standard I/O device (console) anymore. The input or output will be determined by the file variables used as the first parameters.
Just remember this, if a file variable is specified as the first parameter in the READLN or WRITELN statements, the input or output would not be from the console anymore. It would be from a file. The file could be a disk file, or one of DOS' standard files (PRN,CON).
Binary files.
program UseBinaryFiles;
uses CRT;
var
RealF : file of real;
IntegerF : file of integer;
OneReal : real;
OneInteger : integer;
WhichItem : integer;
I : integer;
begin { main program }
Assign( RealF, 'REALS.DAT' );
Assign( IntegerF, 'INTEGER.DAT');
Rewrite( RealF ); Rewrite( IntegerF );
OneReal := 0.0; OneInteger := 0;
for I := 1 to 100 do
begin
OneReal := OneReal + 1.5;
OneInteger := OneInteger + 2;
Write( RealF, OneReal );
Write( IntegerF, OneInteger );
end;
Write('Enter which integer/real to view (1..100) : ');
ReadLn( WhichItem );
WhichItem := WhichItem - 1;
Seek( RealF, WhichItem );
Seek( IntegerF, WhichItem );
Read( RealF, OneReal );
Read( IntegerF, OneInteger );
WriteLn('Real at position ',WhichItem,' is ', OneReal:4:2 );
WriteLn('Integer at position ',WhichItem,' is ', OneInteger );
WriteLn;
WriteLn('End of program.'); ReadLn;
Close( IntegerF ); Close( RealF );
end. { main program }
The declaration for BINARY files is different. A file variable is declared as a FILE OF a certain data type. This means that each component in the file is of that particular data type. The compiler keeps track of the size of each item in the file. This allows the program to access the file randomly (READ or WRITE and at any location). The item which is accessed is determined by the file pointer. The file pointer can be placed at any location in the file by using the SEEK procedure.
The SEEK procedure's first component number is 0. If you were to use SEEK and specify a component number of 3, the pointer will be placed at the 4th component in the file. Read the program carefully and try to determine why the variable WHICHITEM is decreased.
Binary file of record type.
Here is another program which uses a BINARY file but this time the components in the file are records. The declaration of the record is the same. Take a close look at the declaration of the file type.
program UseFileOfRecords;
uses CRT;
type
EmpRec = record
Name : string;
Hw, Hr : real;
Tax : integer;
end;
EmpFileType = file of EmpRec;
var
Employee : EmpRec;
EmpNumber : integer;
EmpFile : EmpFileType;
Choice : char;
procedure OpenFiles;
begin
Assign( EmpFile, 'EMPLOYEE.DAT' );
{$I-} Reset( EmpFile ); {$I+}
if IOResult = 0 then
EmpNumber := filesize( EmpFile ) - 1;
else
begin
Rewrite( EmpFile );
EmpNumber := 0;
end;
end; { OpenFiles }
procedure GetEmpDetails ( var OneEmp : EmpRec );
begin
with OneEmp do
begin
Write('Enter Employee Name : '); ReadLn( Name );
Write('Hours Worked : '); ReadLn( Hw );
Write('Hourly Rate : '); ReadLn( Hr );
Write('Tax Deduction % : '); ReadLn( Tax );
end;
end; { GetEmpDetails }
procedure DisplayDetails( OneEmp : EmpRec );
var
TaxDeduct : real;
Net, Gross : real;
begin
with OneEmp do
begin
Gross := Hw * Hr;
TaxDeduct := Gross * ( Tax / 100 );
Net := Gross - TaxDeduct;
WriteLn('Enter Employee Name : ', Name );
WriteLn('Hours Worked : ', Hw:4:2 );
WriteLn('Hourly Rate : ', Hr:4:2 );
WriteLn('Tax Deductions : ', TaxDeduct:4:2 );
WriteLn('Gross Pay : ', Gross:4:2 );
WriteLn('Net Pay : ', Net:4:2 );
end;
end; { DisplayDetails }
procedure AddNewEmployee;
var
Temp : EmpRec;
begin
Inc( EmpNumber );
GetEmpDetails( Temp );
Seek( EmpFile, EmpNumber );
Write( EmpFile, Temp );
end; { AddNewEmployee }
procedure SearchForEmployee;
var
EmpNum : integer;
begin
Write('Enter Employee Number (1..',EmpNumber,' only) : ');
ReadLn( EmpNum );
if ( EmpNum <= EmpNumber ) and ( EmpNum > 0 ) then
begin
Seek( EmpFile, EmpNum );
Read( EmpFile, Employee );
DisplayDetails( Employee );
end
else
WriteLn('No such employee number.');
end; { SearchForEmployee }
procedure DisplayAllNames;
var
I : integer;
begin
for I := 1 to EmpNumber do
begin
Seek( EmpFile, I );
Read( EmpFile, Employee );
with Employee do
WriteLn( I:4,') ',Name );
end;
end; { DisplayAllNames }
begin { main program }
ClrScr;
OpenFiles;
repeat
WriteLn('[A]dd / [D]isplay / [S]earch / [Q]uit .');
Write('Enter Choice : ');
ReadLn( Choice );
Choice := upcase( Choice );
case Choice of
'A' : AddNewEmployee;
'D' : DisplayAllNames;
'S' : SearchForEmployee;
end; { case Choice }
until Choice = 'Q';
Close( EmpFile );
end. { main program }
If you look closely, you will find that the method for inserting new records into the file is very simple. The records are simply added to the bottom of the file.
When the files are opened, the program will first check for an input/output error. If the file exists, there won't be any I/O errors and the program will proceed to determine the number of records in the file by using the FILESIZE procedure. Remember, FILESIZE returns the number of records in the file and not the number of bytes used by the file. If the file does not exist, it will be created and the number of records is set to zero.
Now look at the ADDNEWEMPLOYEE procedure. It accepts details from the user by calling the procedure GETEMPDETAILS and SEEKs to the end of the file and writes the record at that position. The number of records in the file is then increased by one to indicate a new record has been added.
The procedure to display all employee names (DISPLAYALLNAMES) reads the records in the file, one by one, and displays them on the screen. You can modify this procedure to include a choice of whether a hardcopy is to be printed. If you notice the SEEK procedure just before each READ statement, you will see that each and every record in the file is read because the record numbers in the SEEK statement is determined by the FOR..DO loop which goes from the first to the last record in the file.
The search procedure uses a linear search to look for a wanted name. The SEEK procedure is again important as it must move the file pointer to the proper place for the procedure to read a record and compare the name field of the record read with the name which the user is looking for.
BINARY files can be likened to an array except the storage medium for a file is always on a secondary storage media. BINARY files uses a record number to reference to a file component much like an array using a subscript. For files, the file pointer is positioned by the procedure SEEK unlike an array subscript which is supplied just after the array name.
The most important thing to know about accessing files is the position of the file pointer. If you are not sure about the position of the file pointer at any time, you might not be able to understand the methods used when accessing files. The position of the file pointer, don't forget this, determines where the access (read or write) to the file is going to take place.
Once the methods of accessing files are known, you can proceed to develop programs which uses data files for a more permanent storage. You must know which file to use for which application.
Program UseSaveToFile;
USES Crt;
CONST
MaxRec = 100;
DataFileName = 'C:\emp.dat';
TYPE
RecType = RECORD
Name : String[25];
AccNo : Integer;
END;
ArrType = Array [1..MaxRec] OF RecType;
VAR
Employees : ArrType;
EmpFile : File Of RecType;
TotalRec : Integer;
FUNCTION FileExist(FileName : String) : Boolean;
VAR
F : File of RecType;
BEGIN
{$I-} Reset(F); {$I+} { try to open file }
IF IOResult = 0 THEN { no errors so file exist }
FileExist := TRUE
else { error - so file does not exist }
FileExist := FALSE;
END;
PROCEDURE LoadFromFile;
VAR
OneRec : RecType;
i : Integer;
BEGIN
i := 0;
Assign (EmpFile, DataFileName);
Reset(EmpFile); { open file for reading }
IF FileExist(DataFileName) THEN
While NOT EOF(EmpFile) DO
begin
inc(i);
Read(EmpFile, OneRec);
Employees[i]:= OneRec;
end;
TotalRec := i;
END;
PROCEDURE SaveToFile;
VAR
i : Integer;
BEGIN
Assign(EmpFile, DataFileName);
ReWrite(EmpFile); { open file for writing }
FOR i := 1 to TotalRec DO
Write(EmpFile, Employees[i]); { save one record }
Close(EmpFile);
WriteLn(TotalRec, ' saved');
END;
PROCEDURE GetRecords(VAR Emp : ArrType);
VAR
Ch : Char;
BEGIN
REPEAT
WriteLn('Record :',TotalRec+1);
Write('Name : '); ReadLn(Emp[TotalRec + 1].Name);
Write('Account No: '); ReadLn(Emp[TotalRec + 1].AccNo);
WriteLn;
Inc(TotalRec);
Write('Continue (Y/N) :'); ch := ReadKey;
WriteLn;
UNTIL (UpCase(Ch) = 'N');
END;
BEGIN { main }
ClrScr;
GetRecords(Employees);
SaveToFile;
END. { main }
Program UseReadFromFile;
USES Crt;
CONST
MaxRec = 100;
DataFileName = 'C:\emp.dat';
TYPE
RecType = RECORD
Name : String[25];
AccNo : Integer;
END;
ArrType = Array [1..MaxRec] OF RecType;
VAR
Employees : ArrType;
EmpFile : File Of RecType;
TotalRec : Integer;
FUNCTION FileExist(FileName : String) : Boolean;
VAR
F : File of RecType;
BEGIN
{$I-} Reset(F); {$I+} { try to open file }
IF IOResult = 0 THEN { no errors so file exist }
FileExist := TRUE
else { error - so file does not exist }
FileExist := FALSE;
END;
PROCEDURE LoadFromFile;
VAR
OneRec : RecType;
i : Integer;
BEGIN
i := 0;
Assign (EmpFile, DataFileName);
Reset(EmpFile); { open file for reading }
{ IF FileExist(DataFileName) THEN}
While NOT EOF(EmpFile) DO
begin
inc(i);
Read(EmpFile, OneRec);
Employees[i]:= OneRec;
end;
TotalRec := i;
END;
PROCEDURE ShowRecords(VAR Emp: ArrType);
VAR
I : Integer;
BEGIN
FOR I := 1 to TotalRec DO
begin
Write(i:4,':');
Write(Emp[i].Name:20);
WriteLn(Emp[i].AccNo:20);
end;
END;
BEGIN { main }
ClrScr;
TotalRec := 0;
LoadFromFile;
ShowRecords(Employees);
ReadKey;
END. { main }