Text Files Grade 11

Text files allow your Delphi program to save and load data that persists between runs. Learn the four operations: reading, writing, appending, and checking existence.

T2Term 2 · Reading & Writing Text Files

What Is a Text File?

A text file is a file that stores plain, readable characters with no special formatting — just letters, numbers and line breaks, exactly as you would type them into Notepad. There are no fonts, colours, or hidden layout codes like you would find in a Word document; it is simply text, one line after another.

ANALOGY

Think of a text file as a plain notepad. You can open it, read what is written, add a new line at the bottom, or tear out the page and start fresh. There is nothing fancy inside — just the words you wrote.

Why Do We Use Files?

The variables in your program only live in memory (RAM) while the program is running. The moment you close the program, everything in those variables is gone. A file gives your data persistence — it is saved onto the disk so that it survives after the program closes and is still there the next time you run it.

Example: a marks program that lets a teacher capture results is useless if every mark vanishes when she closes it. By writing the marks to a text file, the data is safely stored and can be read back tomorrow.

Physical vs Logical Filename

When working with files in Delphi there are two "names" to keep straight:

The AssignFile command is what links the two together — it tells Delphi "whenever I use the logical variable F, I really mean the physical file Data.txt on the disk".

ANALOGY

Think of a postbox. The physical filename is the actual postbox standing in the street with a real street address. The logical filename is the label F you stick on your desk that says "this drawer represents that postbox". AssignFile is the act of writing the street address onto your label so the two are connected.

What Each File Command Does

Before looking at the table, here is each command explained in plain words:

Key Procedures and Functions

CommandPurpose
AssignFile(F, 'file.txt')Links the file variable F to a file on disk
Reset(F)Opens existing file for reading (from beginning)
Rewrite(F)Creates/overwrites a file for writing
Append(F)Opens existing file for writing — adds to end
CloseFile(F)Saves and closes the file
ReadLn(F, S)Reads one line from file into variable S
WriteLn(F, S)Writes variable S as one line to the file
EOF(F)Returns True when the end of the file is reached
FileExists('file.txt')Returns True if the file exists on disk

Reading a Text File

Delphi — read all lines
var
  F: TextFile;
  S: string;
begin
  if FileExists('Data.txt') then
  begin
    AssignFile(F, 'Data.txt');
    Reset(F);                          // open for reading
    while not EOF(F) do
    begin
      ReadLn(F, S);
      memOut.Lines.Add(S);             // process each line
    end;
    CloseFile(F);
  end
  else
    ShowMessage('File not found!');
end;

Quick Alternative — LoadFromFile and SaveToFile

A Memo or RichEdit's Lines property has two built-in methods that skip the whole AssignFile/Reset/ReadLn loop when you just want to dump a text file straight onto the screen (or the screen straight into a file). One line does the job of the entire loop above.

Delphi — load and save a whole file in one line
memOutput.Lines.Clear;
memOutput.Lines.LoadFromFile('Data.txt');   // reads the whole file into the Memo

// ... later, to save the Memo's content back to a file:
memOutput.Lines.SaveToFile('Output.txt');   // writes every line in the Memo to a file
When to use which method

LoadFromFile/SaveToFile are fast and simple, but they give you no control over each line — you cannot validate, split or calculate anything while the file loads. Use them only when you want to display or save a file as-is. As soon as a question asks you to process the data (split fields, calculate a total, search for a value), you need the AssignFile/Reset/ReadLn/EOF loop instead.

Checking a File Exists with try..except

Using FileExists is the simplest way to avoid a crash. A second method is to try to open the file and catch the error if it is missing. A try..except block runs the risky code in the try part; if an exception happens (for example the file does not exist, so Reset fails), control jumps straight to the except part instead of crashing the program.

Delphi — try..except when reading
var
  F: TextFile;
  S: string;
begin
  AssignFile(F, 'Data.txt');
  try
    Reset(F);                          // raises an error if the file is missing
    while not EOF(F) do
    begin
      ReadLn(F, S);
      memOut.Lines.Add(S);
    end;
    CloseFile(F);
  except
    on E: EInOutError do
      ShowMessage('Could not open the file: ' + E.Message);
  end;
end;
FileExists vs try..except

FileExists checks before opening and lets you avoid the problem; try..except reacts after an error occurs. try..except also catches other I/O problems (e.g. a corrupt or locked file), so the two are often combined: test with FileExists, then wrap the file operations in try..except as a safety net. The exception type for file errors is EInOutError.

Writing to a Text File (Overwrite)

Delphi — create / overwrite
var
  F: TextFile;
begin
  AssignFile(F, 'Output.txt');
  Rewrite(F);                          // creates or overwrites
  WriteLn(F, sName + ',' + sSurname + ',' + IntToStr(iAge));
  CloseFile(F);
end;

Appending to a Text File

Delphi — add to end
var
  F: TextFile;
begin
  AssignFile(F, 'Data.txt');
  Append(F);                           // adds to existing file
  WriteLn(F, 'New data line');
  CloseFile(F);
end;

Reading CSV Data into Arrays

Delphi — read Name,Mark pairs
var
  F: TextFile;
  sLine, sName: string;
  iMark, iCount, iPos: Integer;
begin
  iCount := 0;
  AssignFile(F, 'marks.txt');
  Reset(F);
  while not EOF(F) do
  begin
    ReadLn(F, sLine);
    iPos := Pos(',', sLine);
    Inc(iCount);
    arrNames[iCount] := Copy(sLine, 1, iPos - 1);
    arrMarks[iCount] := StrToInt(Copy(sLine, iPos + 1, Length(sLine)));
  end;
  CloseFile(F);
end;
Always CloseFile!

Forgetting CloseFile(F) means the file remains locked and data may not be saved properly.

Worked Example — Putting It All Together

Exam questions rarely test one skill on its own. A typical Paper 1 question gives you a text file, asks you to extract and manipulate each line, store the parts in parallel arrays, and then process them. Here is that whole pattern in one example.

The scenario

A text file marks.txt stores one learner per line as Surname,Name,Mark:

marks.txt
Coetzee,Mari,72
Naidoo,Priya,88
Botha,Jan,64
Khumalo,Sipho,91

Task: read the file into parallel arrays, then (a) display each learner as M. Coetzee: 72%, (b) show the class average, and (c) find the top learner.

Step 0 — Declare the parallel arrays

Delphi — global declarations
const
  Max = 100;                 // most learners the arrays can hold
var
  arrSurname : array[1..MAX] of string;
  arrName    : array[1..MAX] of string;
  arrMark    : array[1..MAX] of integer;
  iCount     : integer;          // how many learners were actually read

The three arrays are parallel: index [2] in each one belongs to the same learner (Priya Naidoo, 88).

Step 1 — Read the file & split each line

Read one line at a time. Use Pos to find each comma, then Copy and Delete to chop the line into its three fields.

Delphi — read into parallel arrays
var
  F     : TextFile;
  sLine : string;
  iPos  : integer;
begin
  iCount := 0;
  if not FileExists('marks.txt') then
  begin
    ShowMessage('File not found!');
    Exit;
  end;
  AssignFile(F, 'marks.txt');
  Reset(F);
  while not EOF(F) do
  begin
    ReadLn(F, sLine);                      // e.g. 'Coetzee,Mari,72'
    Inc(iCount);
    // field 1 - surname (up to the first comma)
    iPos := Pos(',', sLine);
    arrSurname[iCount] := Copy(sLine, 1, iPos - 1);   // 'Coetzee'
    Delete(sLine, 1, iPos);                          // -> 'Mari,72'
    // field 2 - first name (up to the next comma)
    iPos := Pos(',', sLine);
    arrName[iCount] := Copy(sLine, 1, iPos - 1);      // 'Mari'
    Delete(sLine, 1, iPos);                          // -> '72'
    // field 3 - whatever remains is the mark
    arrMark[iCount] := StrToInt(sLine);                // 72
  end;
  CloseFile(F);
end;

Step 2 — Manipulate the strings for display

Build the initial from the first letter of the first name with Copy(name, 1, 1).

Delphi — display "M. Surname: 72%"
var
  i        : integer;
  sInitial : string;
begin
  for i := 1 to iCount do
  begin
    sInitial := Copy(arrName[i], 1, 1);            // 'M'
    memOut.Lines.Add(sInitial + '. ' + arrSurname[i]
                     + ': ' + IntToStr(arrMark[i]) + '%');
  end;
end;

Output: M. Coetzee: 72%, P. Naidoo: 88%, J. Botha: 64%, S. Khumalo: 91%

Step 3 — Class average

(Assume iTotal : integer and rAverage : real are declared.)

Delphi — average of the mark array
iTotal := 0;
for i := 1 to iCount do
  iTotal := iTotal + arrMark[i];
rAverage := iTotal / iCount;
ShowMessage('Class average: ' +
            FloatToStrF(rAverage, ffFixed, 4, 1) + '%');

Step 4 — Find the top learner

Search arrMark for the highest value, remembering its index. Because the arrays are parallel, that same index gives you the winner's name and surname too.

Delphi — highest mark via parallel arrays
iTopPos := 1;
for i := 2 to iCount do
  if arrMark[i] > arrMark[iTopPos] then
    iTopPos := i;
ShowMessage('Top learner: ' + arrName[iTopPos] + ' ' + arrSurname[iTopPos]
            + ' (' + IntToStr(arrMark[iTopPos]) + '%)');
What this one question tests

In a single question you have used text file handling (FileExists, Reset, ReadLn, EOF, CloseFile), string manipulation (Pos, Copy, Delete), parallel arrays (three arrays sharing one index), and processing (an aggregate and finding a maximum). Remember the dinner-set rule: the winning index carries the name, surname and mark together.

And back out — writing the results to a new file

Often the last step is to write your processed results to a new text file. Use Rewrite to create it, WriteLn to add each line, and CloseFile to save. Here we write a pass/fail report from the arrays built above.

Delphi — write report.txt
var
  Fout    : TextFile;
  i       : integer;
  sResult : string;
begin
  AssignFile(Fout, 'report.txt');
  Rewrite(Fout);                         // creates (or overwrites) the file
  for i := 1 to iCount do
  begin
    if arrMark[i] >= 50 then
      sResult := 'PASS'
    else
      sResult := 'FAIL';
    WriteLn(Fout, arrSurname[i] + ',' + IntToStr(arrMark[i]) + ',' + sResult);
  end;
  CloseFile(Fout);                       // saves and closes the file
end;

That completes the full read → process → write cycle: read the data in, work with it in arrays, then save the results back out to a new file.

Working with More Than One Text File at a Time

A practical question will sometimes ask you to read from one existing file and, in the same pass, sort its lines into two or more new files depending on a condition. Reusing the marks.txt file from the worked example above, here is a program that reads it once and writes each learner's surname into either Pass.txt or Fail.txt depending on their mark. The file being read only ever needs Reset; every file being created or overwritten needs its own Rewrite call, and all three files stay open together inside a single while not EOF loop.

Delphi — split marks.txt into Pass.txt and Fail.txt
var
  Fin, FPass, FFail : TextFile;
  sLine, sSurname     : string;
  iPos, iMark          : integer;
begin
  AssignFile(Fin, 'marks.txt');
  AssignFile(FPass, 'Pass.txt');
  AssignFile(FFail, 'Fail.txt');
  try
    Reset(Fin);                           // only the file being read needs a try..except
  except
    ShowMessage('marks.txt could not be opened');
    Exit;
  end;
  Rewrite(FPass);                         // creates/overwrites the two output files
  Rewrite(FFail);

  while not EOF(Fin) do
  begin
    ReadLn(Fin, sLine);                   // e.g. 'Coetzee,Mari,72'
    iPos := Pos(',', sLine);
    sSurname := Copy(sLine, 1, iPos - 1);
    Delete(sLine, 1, iPos);
    iPos := Pos(',', sLine);
    Delete(sLine, 1, iPos);           // discard the first name, keep the mark
    iMark := StrToInt(sLine);
    if iMark >= 50 then
      WriteLn(FPass, sSurname)
    else
      WriteLn(FFail, sSurname);
  end;
  CloseFile(Fin);
  CloseFile(FPass);
  CloseFile(FFail);
end;
Rewrite wipes existing data

If a file with that name already exists, Rewrite does not simply open it — it empties it first. So if Pass.txt already held data from a previous run, that data is gone the instant Rewrite(FPass) executes. Point Rewrite only at the files you genuinely intend to (re)create; call it on the wrong variable by mistake and there is no way to get the lost data back. The file you are reading from should only ever be opened with Reset.

Building a filename at runtime

A physical filename does not have to be a fixed string — it can be assembled from user input or a variable, which is useful when every record needs its own file. If the user types a computer number into edtNumber, for example, build the filename before linking it with AssignFile:

Delphi — dynamic filename
sFilename := 'Computer' + edtNumber.Text + '.txt';   // e.g. 'Computer34.txt'
AssignFile(tCompFile, sFilename);

Writing Data to a Text File in Neat Columns

A field width indicator reserves a fixed number of character positions for a value and right-justifies it inside them, which is what makes data on separate lines line up into columns. Add a colon and a number straight after the value inside a WriteLn statement.

Delphi — field width indicator
sSurname := 'Pillay';
WriteLn(tFile, sSurname:10);   // 'Pillay' padded with 4 leading spaces to fill 10 positions

A field width on its own only right-justifies. To left-align a value instead, pad an empty string out to whatever width is still needed (using Length) and write that padding before the next field:

Delphi — left-align with Length
sName := 'Ayesha';
sSurname := 'Pillay';
WriteLn(tFile, sName, ' ':10 - Length(sName), sSurname);   // surname always starts at column 11

To align decimal values so the decimal points line up under each other, use the Format function to first convert the number into a fixed-width string, then write that string with WriteLn.

Format codeMeaning
%Marks the start of a formatting instruction (not normal text)
10The final string must be 10 characters wide
2Show 2 digits after the decimal point
fThe value is a floating-point (real) number
mSame as f, but also adds a currency symbol (e.g. R)
Delphi — Format function
WriteLn(tFile, Format('%10.2f', [rValue]));    // e.g. '     84.41'
WriteLn(tFile, Format('%12.2m', [rPrice]));   // e.g. '      R14.60'
Use a fixed-width font to check your columns

Proportional fonts (like Arial) give each character a different width, so columns that line up perfectly in your code can look uneven when displayed. Set the output component's font to a fixed-width font such as Courier New so every character takes up exactly the same space and your columns line up visually.