Marco Web Center

[an error occurred while processing this directive]
Essential Pascal Cover

The cover of the 4th edition of Essential Pascal, the first available in print (and PDF) on Lulu.com.

Marco Cantù's
Essential Pascal

Chapter 12
Files in the Pascal Language

One of the peculiarities of Pascal compared with other programming languages is its built-in support for files. As you might recall from Chapter 2, the language has a file keyword, which is a type specifier, like array or record. You use file to define a new type, and then you can use the new data type to declare new variables:

type
  IntFile: file of Integers;
var
  IntFile1: IntFile;

It is also possible to use the file keyword without indicating a data type, to specify an untyped file. Alternatively, you can use the TextFile type, defined in the System units to declare files of ASCII characters. Each kind of file has its own predefined routines, as we will see later in this chapter.

Routines for Working with Files

Once you have declared a file variable, you can assign it to a real file in the file system using the AssignFile method. The next step is usually to call Reset to open the file for reading at the beginning, Rewrite to open (or create) it for writing, and Append to add new items to the end of the file without removing the older items. Once the input or output operations are done, you should call CloseFile.

As an example look at the folloing code, which simply saves some numbers to a file:

type
  IntFile: file of Integers;
var
  IntFile1: IntFile;
begin
  AssignFile (IntFile1, 'c:/tmp/test.my')
  Rewrite (IntFile1);
  Write (IntFile1, 1);
  Write (IntFile1, 2);
  CloseFile (IntFile1);
end;

The CloseFile operation should typically be done inside a finally block, to avoid leaving the file open in case the file handling code generates an exception. Actually file based operations generate exceptiosn or not depending on the $I compiler settings. In case the system doesn't raise exceptions, you can check the IOResult global variable to see if anything went wrong.

Delphi includes many other file management routines, some of whcih are includes in the list below:

Append FileClose Flush
AssignFile FileCreate GetDir
BlockRead FileDateToDateTime IOResult
BlockWrite FileExists MkDir
ChangeFileExt FileGetAttr Read
CloseFile FileGetDate Readln
DateTimeToFileDate FileOpen Rename
DeleteFile FilePos RenameFile
DiskFree FileRead Reset
DiskSize FileSearch Rewrite
Eof FileSeek RmDir
Eoln FileSetAttr Seek
Erase FileSetDate SeekEof
ExpandFileName FileSize SeekEoln
ExtractFileExt FileWrite SetTextBuf
ExtractFileName FindClose Truncate
ExtractFilePath FindFirst Write
FileAge FindNext Writeln

Not all of these routines are defined in standard Pascal, but many of them have been part of Borland Pascal for a long time. You can find detailed information about these routines in Delphi's Help files. Here, I'll show you three simple examples to demonstrate how these features can be used.

Handling Text Files

One of the most commonly used file formats is that of text files. As I mentioned before, Delphi has some specific support for text files, most notably the TextFile data type defined by the System unit. Ignoring the fact that Delphi's string lists can autoamtically save themselves to a file (with the SaveToFile method, based on the use of streams), you could easily save the content of a string list to a TextFile by writing the folloing code (in which the file name is requested to the user, using Delph's SaveDialog component):

var
  OutputFile: TextFile;
  I: Integer;
begin
  // choose a file
  if SaveDialog1.Execute then
  begin
    // output the text to a file
    AssignFile (OutputFile, SaveDialog1.FileName);
    Rewrite (OutputFile);
    try
      // save listbox data to file
	  for I := 0 to ListBox1.Items.Count - 1 do 
	    Writeln (OutputFile, ListBox1.Items[I]);
    finally
      CloseFile (OutputFile);
    end;
  end;
end;

Instead of being connected to a physical file, a Pascal file type variable can be hooked directly to the printer, so that the output will be printed instead of beign saved to a file. To accomplish this, simple use the AssignPrn procedure. For example, in the code above you could replace the line AssignFile (OutputFile, SaveDialog1.FileName); with the line AssignPrn (OutputFile);

A Text File Converter

Up to now we've seen simple examples of creating new files. In our next example, we'll process an existing file, creating a new one with a modified version of the contents. The program, named Filter, can convert all the characters in a text file to uppercase, capitalize only the initial word of each sentence, or ignore the characters from the upper portion of the ASCII character set.

The form of the program has two read-only edit boxes for the names of the input and output files, and two buttons to select input and output files using the standard dialog boxes. The form's lower portion contains a RadioGroup component and a bitmap button (named ConvertBitBtn) to apply the current conversion to the selected files. The radio group has three items, as you can see from the following portion of the form's textual description:

object RadioGroup1: TRadioGroup
  Caption = 'Conversion'
  Items.Strings = (
    '&Uppercase'
    'Capitalize &sentences'
    'Remove s&ymbols')

The user can click on the two buttons to choose the names of the input and output files, displayed in the two edit boxes:

procedure TForm1.Button1Click(Sender: TObject);
begin
  if OpenDialog1.Execute then
    Edit1.Text := OpenDialog1.Filename;
end;

The second button activates the SaveDialog1 dialog box. The real code of the example is in the three conversion routines that are called by the bitmap button's OnClick event-handler. These calls take place inside a case statement in the middle of the ConvertBitBtn button's OnClick handler:

          case RadioGroup1.ItemIndex of
            0: ConvUpper;
            1: ConvCapitalize;
            2: ConvSymbols;
          end;

Once again, you can see the entire source code among the download files. Before calling one of the conversion procedures, the ConvertBitBtnClick method displays a dialog box (ConvertForm) with a ProgressBar component, to show the user that the conversion is taking place (as you can see in Figure 12.1). This method does most of the work related to handling the files-it opens the input file as a file of bytes (a file storing data as plain bytes) the first time, so that it can use the FileSize procedure, which is not available for text files. Then this file is closed and reopened as a text file.

FIGURE 12.1: The conversion procedures update the secondary form's progress bar to let the user see the percentage of the file already processed.

Since the program opens two files, and each of these operations can fail, it uses two nested try blocks to ensure a high level of protection, although using the standard dialog boxes to select file names already provides a good confirmation of file selection. Now, let's take a look at one of the conversion routines in detail. The simplest of the three conversion routines is ConvUpper, which converts every character in the text file to uppercase. Here is its code:

procedure TForm1.ConvUpper;
var
  Ch: Char;
  Position: LongInt;
begin
  Position := 0;
  while not Eof (FileIn) do
  begin
    Read (FileIn, Ch);
    Ch := UpCase (Ch);
    Write (FileOut, Ch);
    Inc (Position);
    ConvertForm.ProgressBar1.Position :=
      Position * 100 div FileLength;
    Application.ProcessMessages;
  end;
end;

This method reads each character from the source file until the program reaches the end of the file (Eof). Each single character is converted and copied to the output file. As an alternative, it is possible to read and convert one line at a time (that is, a string at a time) using string handling routines. This will make the program significantly faster. The approach I've used here is reasonable only for an introductory example.

The conversion procedure's actual code, however, is complicated by the fact that it has to update the dialog box's progress bar. At each step of the conversion, a long integer variable with the current position in the file is incremented. This variable's value is used to compute the percentage of work completed, as you can see in the code above.

The conversion procedure for removing symbols is very simple:

while not Eof (FileIn) do
begin
  Read (FileIn, Ch);
  if Ch < Chr (127) then
    Write (FileOut, Choose);
  ...

The procedure used to capitalize the text, in contrast, is really a complex piece of code, which you can find in the code of the example. The conversion is based on a case statement with four branches:

  • If the letter is uppercase, and it is the first letter after an ending punctuation mark (as indicated by the Period Boolean variable), it is left as is; otherwise, it is converted to lowercase. This conversion is not done by a standard procedure, simply because there isn't one for single characters. It's done with a low-level function I've written, called LowCase.
  • If the letter is lowercase, it is converted to uppercase only if it was at the beginning of a new sentence.
  • If the character is an ending punctuation mark (period, question mark, or exclamation mark), Period is set to True.
  • If the character is anything else, it is simply copied to the destination file, and Period is set to False.

Figure 12.2 shows an example of this code's effect; it shows a text file before and after the conversion. This program is far from adequate for professional use, but it is a first step toward building a full-scale case conversion program. Its biggest drawbacks are that it frequently converts proper nouns to lowercase, and capitalizes any letter after a period (even if it's the first letter of a filename extension).

FIGURE 12.2: The result of running the Filter example's Capitalize conversion.

Saving Generic Data

In addition to using text files, you can save other data types to a file, including integers, real numbers, arrays, and records. Using a custom file type instead of a text file may be an advantage because it might take less space (the textual representation of a number usually takes much more space than its binary value), but this approach won't let the user browse through the files using a text editor (which might be an advantage, too).

How do you save a series of integers to a file? First you have to define a file as shown:

SaveFile: file of Integer;

Then you need to assign the real file to the file variable, open the file, operate on it , and close it. For example the following code saves all the numeric data collected in a 5x4 string grid:

      {save to the current file}
      AssignFile (SaveFile, CurrentFile);
      Rewrite (SaveFile);
      try
        {write the value of each grid element}
        for I := 1 to 5 do
          for J := 1 to 4 do
          begin
            Value := StrToIntDef (Trim (
              StringGrid1.Cells [I, J]), 0);
            Write (SaveFile, Value);
          end;
      finally
        CloseFile (SaveFile);
      end;

To save the data to a file, the program saves each value of the string grid (after converting it into a number). To accomplish this, the program uses two nested for loops to scan the grid. Notice the use of the temporary Value variable: the Write and Read procedures require a parameter passed by reference (var), so you cannot pass the property of a Delphi component, since it doesn't correspond directly to a memory location.

Of course, the data should be read in the same order it is written, as you can see in the Open1Click method:

    {load from the current file}
    AssignFile (LoadFile, CurrentFile);
    Reset (LoadFile);
    try
      {read the value of each grid element}
      for I := 1 to 5 do
        for J := 1 to 4 do
        begin
          Read (LoadFile, Value);
          StringGrid1.Cells [I, J] := IntToStr(Value);
        end;
    finally
      CloseFile (LoadFile);
    end;

From Files to Streams

Although direct handling of files, using the traditioanl Pascal-langauge appraoch is certainly still an interesting techcnique, a strongly urge you to use streams (the TStream and derived classes) to handle any complex files. Streams represent virtual files, which can be mapped to physical files, to a memory block, to a socket, or any other continuous series of bytes. You can find more on streams in the Delphi help file and in my Mastering Delphi book.

Conclusion

At least for the moment, this chapter on files is the last of the book. Feel free to email me your comment and requests.

If after this introduction on the Pascal language you want to delve into the object-oriented elements of Object Pascal in Delphi, you can refer to my published book Mastering Delphi 5 (Sybex, 1999). For more information on this and more advanced books of mine (and of other authors as well) you can refer to my web site, www.marcocantu.com. The same site hosts updated versions of this book, and its examples. Keep also an eye for the companion book Essential Delphi.

Back to the Cover Page