Reading File Content

by Martin Seiler (modified: 2007 Feb 26)

In this tutorial I'll briefly explain how one can load the content of an ASCII file into a single string.

The deferred class FILE cannot be used directly as it is deferred.

The descendant PLAIN_TEXT_FILE is what we're actually looking for.

Basic steps

The basic steps are the following: local l_file: PLAIN_TEXT_FILE l_content: STRING do create l_file.make_create_read_write (a_path) l_file.read_stream (l_file.count) l_content := l_file.last_string.twin l_file.close end

Let's have a brief look at each single line:

Opening a file

We instantiate the class and open the file directly for read. Doing it like that has the advantage that it is just one line. create l_file.make_create_read_write (a_path) Note: It might happen that the postcondition is violated if you don't have the permission to do the file operation for the path specified. We will later take this into account.

Reading from a file

l_file.read_stream (l_file.count) There are several other read methods available. For example you can use read_line to read a single line of a given file. l_content := l_file.last_string.twin As the command/query separation principle is implemented, the result is always stored in last_.... Reading an integer from an ASCII file would look the following:\ l_file.read_integer l_integer := l_file.last_integer
Note that we perform a call to twin for instances of type string as there is only one buffer per file object and it is overwritten by the next read command. If we do a twin we make sure that we don't have any surprising side effects later. So in general I recommend to do it for all non expanded types.

Closing a file

l_file.close Last but not least we close the file to free system resources. However, if we simply set the reference to Void this will do job as well, as the garbage collector calls dispose before he collects the object, which in turn calls close if the file is still open.

Better error handling

The creation feature we used so far initializes the object and tries to open the file right away. If the files does not exist an attempt to create it will be made. Which may possibly fail if you don't have the proper rights to do so. Still, the file will be empty and this is maybe not what the original intention of the programmer was.

To get more control over the whole process I recommend a different, more sophisticated way of achieving the same: local l_file: PLAIN_TEXT_FILE do create l_file.make (a_path) -- We perform several checks until we make a real attempt to open the file. if not l_file.exists then print ("error: '" + a_path + "' does not exist%N") else if not l_file.is_readable then print ("error: '" + a_path + "' is not readable.%N") else l_file.open_read l_file.read_stream (l_file.count) Result := l_file.last_string.twin l_file.close end end end
This feature checks several possible error conditions and prints error messages. The pattern will be the same in most cases, but the error handling will most likely depend on you application. So you could write a feature suited for your application with proper error handling and then simply reuse that feature whenever you need to open a file. If you don't want to copy paste the code all the time and just change the two error handling lines a more fine tuned class could implement the observer pattern implemented by using agents.