335 lines
13 KiB
Plaintext
335 lines
13 KiB
Plaintext
/* $Id$
|
|
|
|
Harbour Project source code
|
|
|
|
A class that reads a file one line at a time
|
|
|
|
Copyright 1999 David G. Holm <dholm@jsd-llc.com>
|
|
www - http://www.harbour-project.org
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
(at your option) any later version, with one exception:
|
|
|
|
The exception is that if you link the Harbour Runtime Library (HRL)
|
|
and/or the Harbour Virtual Machine (HVM) with other files to produce
|
|
an executable, this does not by itself cause the resulting executable
|
|
to be covered by the GNU General Public License. Your use of that
|
|
executable is in no way restricted on account of linking the HRL
|
|
and/or HVM code into it.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA (or visit
|
|
their web site at http://www.gnu.org/).
|
|
|
|
V 1.1 David G. Holm Committed to CVS.
|
|
V 1.0 David G. Holm Initial version.
|
|
*/
|
|
|
|
#include "fileio.ch"
|
|
|
|
#define oF_ERROR_MIN 1
|
|
#define oF_CREATE_OBJECT 1
|
|
#define oF_OPEN_FILE 2
|
|
#define oF_READ_FILE 3
|
|
#define oF_CLOSE_FILE 4
|
|
#define oF_ERROR_MAX 4
|
|
#define oF_DEFAULT_READ_SIZE 4096
|
|
|
|
/* $DOC$
|
|
* $FUNCNAME$
|
|
* TFileRead()
|
|
* $CATEGORY$
|
|
* Harbour Tools
|
|
* $ONELINER$
|
|
* Read a file one line at a time
|
|
* $SYNTAX$
|
|
* oFile := TFileRead():New( <cFileName> [, <nReadSize> ] )
|
|
* $ARGUMENTS$
|
|
* cFileName is the required name of the file to be read.
|
|
* nReadSize is the optional size to use when reading from the file.
|
|
* The default value is 4096 and the allowed range is 1 through 65535.
|
|
* Any value outside of this range causes the default value to be used.
|
|
* $RETURNS$
|
|
* An instance of the File Reader class
|
|
* $DESCRIPTION$
|
|
* TFileRead() is used to access a file one line at a time. You must
|
|
* specify the name of the file when an instance of the class is created.
|
|
* The class data should be considered private to the class.
|
|
* The class methods are as follows:
|
|
* New() Creates a new instance of the TFileRead class.
|
|
* Open([<nFlags>]) Opens the file for reading. The optional nFlags
|
|
* parameter can use any of the FOPEN() flags from
|
|
* fileio.ch. The default is FO_READ + FO_SHARED.
|
|
* Calling this method when the file is already
|
|
* open causes the next ReadLine() to start over
|
|
* from the beginning of the file.
|
|
* Close() Closes the file.
|
|
* ReadLine() Returns one line from the file, stripping the
|
|
* newline characters. The following sequences are
|
|
* treated as one newline: 1) CR CR LF; 2) CR LF;
|
|
* 3) LF; and 4) CR. Note: LF CR is 2 newlines.
|
|
* Name() Returns the name of the file.
|
|
* IsOpen() Returns .T. if the file is open.
|
|
* MoreToRead() Returns .T. if there are more lines to be read
|
|
* (think of it as an inverse EOF function).
|
|
* Error() Returns .T. if an error has occurred.
|
|
* ErrorNo() Returns the current error code.
|
|
* ErrorMsg([<cPre>]) Returns a formatted error message.
|
|
* $EXAMPLES$
|
|
* #ifdef __HARBOUR__
|
|
* #define NEW_LINE CHR( 10 )
|
|
* #else
|
|
* #define NEW_LINE CHR( 13 ) + CHR( 10 )
|
|
* #endif
|
|
* #include "fileio.ch"
|
|
*
|
|
* PROCEDURE Main( cFile )
|
|
* LOCAL oFile := TFileRead():New( cFile )
|
|
*
|
|
* oFile:Open()
|
|
* IF oFile:Error()
|
|
* QOUT( oFile:ErrorMsg( "FileRead: " ) )
|
|
* ELSE
|
|
* WHILE oFile:MoreToRead()
|
|
* OUTSTD( oFile:ReadLine() )
|
|
* OUTSTD( NEW_LINE )
|
|
* END WHILE
|
|
* oFile:Close()
|
|
* END IF
|
|
* QUIT
|
|
* $TESTS$
|
|
* See Examples
|
|
* $STATUS$
|
|
* C
|
|
* $COMPLIANCE$
|
|
* This is a new Harbour Tools class
|
|
* $SEEALSO$
|
|
* TClass
|
|
* $END$
|
|
*/
|
|
|
|
FUNCTION TFileRead()
|
|
STATIC oClass
|
|
|
|
IF oClass == NIL
|
|
oClass := TClass():New( "TFile" ) // New class
|
|
oClass:AddClassData( "cFile" ) // The filename
|
|
oClass:AddClassData( "nHan" ) // The open file handle
|
|
oClass:AddClassData( "lEOF" ) // The end of file reached flag
|
|
oClass:AddClassData( "nError" ) // The current file error code
|
|
oClass:AddClassData( "nLastOp" ) // The last operation done (for error messages)
|
|
oClass:AddClassData( "cBuffer" ) // The readahead buffer
|
|
oClass:AddClassData( "nReadSize" ) // How much to add to the readahead buffer on
|
|
// each read from the file
|
|
|
|
oClass:AddMethod( "New", @f_new() ) // Create a new class instance
|
|
oClass:AddMethod( "Open", @f_open() ) // Open the file for reading
|
|
oClass:AddMethod( "Close", @f_close() ) // Close the file when done
|
|
oClass:AddMethod( "ReadLine", @f_read() ) // Read a line from the file
|
|
oClass:AddMethod( "Name", @f_name() ) // Retunrs the file name
|
|
oClass:AddMethod( "IsOpen", @f_is_open() ) // Returns .T. if file is open
|
|
oClass:AddMethod( "MoreToRead", @f_more() ) // Returns .T. if more to be read
|
|
oClass:AddMethod( "Error", @f_error() ) // Returns .T. if error occurred
|
|
oClass:AddMethod( "ErrorNo", @f_error_no() ) // Returns current error code
|
|
oClass:AddMethod( "ErrorMsg", @f_error_msg() ) // Returns formatted error message
|
|
oClass:Create()
|
|
END IF
|
|
|
|
RETURN oClass:Instance()
|
|
|
|
STATIC FUNCTION f_new( cFile, nSize )
|
|
LOCAL oSelf := Qself()
|
|
IF nSize == NIL .OR. nSize < 1
|
|
// The readahead size can be set to as little as 1 byte, or as much as
|
|
// 65535 bytes, but venturing out of bounds forces the default size.
|
|
nSize := oF_DEFAULT_READ_SIZE
|
|
END IF
|
|
oSelf:cFile := cFile // Save the file name
|
|
oSelf:nHan := -1 // It's not open yet
|
|
oSelf:lEOF := .T. // So it must be at EOF
|
|
oSelf:nError := 0 // But there haven't been any errors
|
|
oSelf:nLastOp := oF_CREATE_OBJECT // Because we just created the class
|
|
oSelf:cBuffer := "" // and nothing has been read yet
|
|
oSelf:nReadSize := nSize // But will be in this size chunks
|
|
RETURN oSelf
|
|
|
|
STATIC FUNCTION f_open( nMode )
|
|
LOCAL oSelf := Qself()
|
|
IF oSelf:nHan == -1
|
|
// Only open the file if it isn't already open.
|
|
IF nMode == NIL
|
|
nMode := FO_READ + FO_SHARED // Default to shared read-only mode
|
|
END IF
|
|
oSelf:nLastOp := oF_OPEN_FILE
|
|
oSelf:nHan := FOPEN( oSelf:cFile, nMode ) // Try to open the file
|
|
IF oSelf:nHan == -1
|
|
oSelf:nError := FERROR() // It didn't work
|
|
oSelf:lEOF := .T. // So force EOF
|
|
ELSE
|
|
oSelf:nError := 0 // It worked
|
|
oSelf:lEOF := .F. // So clear EOF
|
|
END IF
|
|
ELSE
|
|
// The file is already open, so rewind to the beginning.
|
|
IF FSEEK( oSelf:nHan, 0 ) == 0
|
|
oSelf:lEOF := .F. // Definitely not at EOF
|
|
ELSE
|
|
oSelf:nError := FERROR() // Save error code if not at BOF
|
|
END IF
|
|
oSelf:cBuffer := "" // Clear the readahead buffer
|
|
END IF
|
|
RETURN oSelf
|
|
|
|
STATIC FUNCTION f_read()
|
|
LOCAL oSelf := Qself()
|
|
LOCAL cLine := ""
|
|
LOCAL nPos
|
|
oSelf:nLastOp := oF_READ_FILE
|
|
IF oSelf:nHan == -1
|
|
oSelf:nError := -1 // Set unknown error if file not open
|
|
ELSE
|
|
// Is there a whole line in the readahead buffer?
|
|
nPos := f_EOL_pos( oSelf )
|
|
WHILE ( nPos <= 0 .OR. nPos > LEN( oSelf:cBuffer ) - 3 ) .AND. !oSelf:lEOF
|
|
// Either no or maybe, but there is possibly more to be read.
|
|
// Maybe means that we found either a CR or an LF, but we don't
|
|
// have enough characters to discriminate between the three types
|
|
// of end of line conditions that the class recognizes (see below).
|
|
cLine := FREADSTR( oSelf:nHan, oSelf:nReadSize )
|
|
IF EMPTY( cLine )
|
|
// There was nothing more to be read. Why? (Error or EOF.)
|
|
oSelf:nError := FERROR()
|
|
IF oSelf:nError == 0
|
|
// Because the file is at EOF.
|
|
oSelf:lEOF := .T.
|
|
END IF
|
|
ELSE
|
|
// Add what was read to the readahead buffer.
|
|
oSelf:cBuffer += cLine
|
|
cLine := ""
|
|
END IF
|
|
// Is there a whole line in the readahead buffer yet?
|
|
nPos := f_EOL_pos( oSelf )
|
|
END WHILE
|
|
// Is there a whole line in the readahead buffer?
|
|
IF nPos <= 0
|
|
// No, which means that there is nothing left in the file either, so
|
|
// return the entire buffer contents as the last line in the file.
|
|
cLine := oSelf:cBuffer
|
|
oSelf:cBuffer := ""
|
|
ELSE
|
|
// Yes. Is there anything in the line?
|
|
IF nPos > 1
|
|
// Yes, so return the contents.
|
|
cLine := LEFT( oSelf:cBuffer, nPos - 1 )
|
|
ELSE
|
|
// No, so return an empty string.
|
|
cLine := ""
|
|
END IF
|
|
// Deal with multiple possible end of line conditions.
|
|
DO CASE
|
|
CASE SUBSTR( oSelf:cBuffer, nPos, 3 ) == CHR( 13 ) + CHR( 13 ) + CHR( 10 )
|
|
// It's a messed up DOS newline (such as that created by a program
|
|
// that uses "\r\n" as newline when writing to a text mode file,
|
|
// which causes the '\n' to expand to "\r\n", giving "\r\r\n").
|
|
nPos += 3
|
|
CASE SUBSTR( oSelf:cBuffer, nPos, 2 ) == CHR( 13 ) + CHR( 10 )
|
|
// It's a standard DOS newline
|
|
nPos += 2
|
|
OTHERWISE
|
|
// It's probably a Mac or Unix newline
|
|
nPos++
|
|
END CASE
|
|
oSelf:cBuffer := SUBSTR( oSelf:cBuffer, nPos )
|
|
END IF
|
|
END IF
|
|
RETURN cLine
|
|
|
|
STATIC FUNCTION f_EOL_pos( oFile )
|
|
LOCAL nCRpos, nLFpos, nPos
|
|
// Look for both CR and LF in the file read buffer.
|
|
nCRpos := AT( CHR( 13 ), oFile:cBuffer )
|
|
nLFpos := AT( CHR( 10 ), oFile:cBuffer )
|
|
DO CASE
|
|
CASE nCRpos == 0
|
|
// If there's no CR, use the LF position.
|
|
nPos := nLFpos
|
|
CASE nLFpos == 0
|
|
// If there's no LF, use the CR position.
|
|
nPos := nCRpos
|
|
OTHERWISE
|
|
// If there's both a CR and an LF, use the position of the first one.
|
|
nPos := MIN( nCRpos, nLFpos )
|
|
END CASE
|
|
RETURN nPos
|
|
|
|
STATIC FUNCTION f_close()
|
|
LOCAL oSelf := Qself()
|
|
oSelf:nLastOp := oF_CLOSE_FILE
|
|
oSelf:lEOF := .T.
|
|
// Is the file already closed.
|
|
IF oSelf:nHan == -1
|
|
// Yes, so indicate an unknown error.
|
|
oSelf:nError := -1
|
|
ELSE
|
|
// No, so close it already!
|
|
FCLOSE( oSelf:nHan )
|
|
oSelf:nError := FERROR()
|
|
oSelf:nHan := -1 // The file is no longer open
|
|
oSelf:lEOF := .T. // So force an EOF condition
|
|
END IF
|
|
RETURN oSelf
|
|
|
|
STATIC FUNCTION f_name()
|
|
LOCAL oSelf := Qself()
|
|
// Returns the filename associated with this class instance.
|
|
RETURN oSelf:cFile
|
|
|
|
STATIC FUNCTION f_is_open()
|
|
LOCAL oSelf := Qself()
|
|
// Returns .T. if the file is open.
|
|
RETURN oSelf:nHan != -1
|
|
|
|
STATIC FUNCTION f_more()
|
|
LOCAL oSelf := Qself()
|
|
// Returns .T. if there is more to be read from either the file or the
|
|
// readahead buffer. Only when both are exhausted is there no more to read.
|
|
RETURN !oSelf:lEOF .OR. !EMPTY( oSelf:cBuffer )
|
|
|
|
STATIC FUNCTION f_error()
|
|
LOCAL oSelf := Qself()
|
|
// Retunrs .T. if an error was recorded.
|
|
RETURN oSelf:nError != 0
|
|
|
|
STATIC FUNCTION f_error_no()
|
|
LOCAL oSelf := Qself()
|
|
// Returns the last error code that was recorded.
|
|
RETURN oSelf:nError
|
|
|
|
STATIC FUNCTION f_error_msg( cText )
|
|
STATIC cAction := {"on", "creating object for", "opening", "reading from", "closing"}
|
|
LOCAL oSelf := Qself()
|
|
LOCAL cMessage, nTemp
|
|
// Has an error been recorded?
|
|
IF oSelf:nError == 0
|
|
// No, so report that.
|
|
cMessage := "No errors have been recorded for " + oSelf:cFile
|
|
ELSE
|
|
// Yes, so format a nice error message, while avoiding a bounds error.
|
|
IF oSelf:nLastOp < oF_ERROR_MIN .OR. oSelf:nLastOp > oF_ERROR_MAX
|
|
nTemp := 1
|
|
ELSE
|
|
nTemp := oSelf:nLastOp + 1
|
|
END IF
|
|
cMessage := IF( EMPTY( cText ), "", cText ) + "Error " + ALLTRIM( STR( oSelf:nError ) ) + " " + cAction[ nTemp ] + " " + oSelf:cFile
|
|
END IF
|
|
RETURN cMessage
|