/* * $Id$ */ /* * Harbour Project source code: * A class that reads a file one line at a time * * Copyright 1999 David G. Holm * 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/). * */ #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 FUNCTION TFileRead() STATIC s_oClass IF s_oClass == NIL s_oClass := TClass():New( "TFile" ) // New class s_oClass:AddClassData( "cFile" ) // The filename s_oClass:AddClassData( "nHan" ) // The open file handle s_oClass:AddClassData( "lEOF" ) // The end of file reached flag s_oClass:AddClassData( "nError" ) // The current file error code s_oClass:AddClassData( "nLastOp" ) // The last operation done (for error messages) s_oClass:AddClassData( "cBuffer" ) // The readahead buffer s_oClass:AddClassData( "nReadSize" ) // How much to add to the readahead buffer on // each read from the file s_oClass:AddMethod( "New", @f_new() ) // Create a new class instance s_oClass:AddMethod( "Open", @f_open() ) // Open the file for reading s_oClass:AddMethod( "Close", @f_close() ) // Close the file when done s_oClass:AddMethod( "ReadLine", @f_read() ) // Read a line from the file s_oClass:AddMethod( "Name", @f_name() ) // Retunrs the file name s_oClass:AddMethod( "IsOpen", @f_is_open() ) // Returns .T. if file is open s_oClass:AddMethod( "MoreToRead", @f_more() ) // Returns .T. if more to be read s_oClass:AddMethod( "Error", @f_error() ) // Returns .T. if error occurred s_oClass:AddMethod( "ErrorNo", @f_error_no() ) // Returns current error code s_oClass:AddMethod( "ErrorMsg", @f_error_msg() ) // Returns formatted error message s_oClass:Create() ENDIF RETURN s_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 ENDIF 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 ENDIF 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 ENDIF 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 ENDIF oSelf:cBuffer := "" // Clear the readahead buffer ENDIF 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. ENDIF ELSE // Add what was read to the readahead buffer. oSelf:cBuffer += cLine cLine := "" ENDIF // 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 := "" ENDIF // 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++ ENDCASE oSelf:cBuffer := SUBSTR( oSelf:cBuffer, nPos ) ENDIF ENDIF 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 ) ENDCASE 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 ENDIF 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() // Returns .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 s_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 ENDIF cMessage := IF( EMPTY( cText ), "", cText ) + "Error " + ALLTRIM( STR( oSelf:nError ) ) + " " + s_cAction[ nTemp ] + " " + oSelf:cFile ENDIF RETURN cMessage