//****************************************************************************
//
// FILE:        DBFile.cpp
//
// WRITTEN BY:  Keimpe
//
// DATE:        1/94
//
// UPDATED:     5/95
//
// REVISION:    $Revision:   2.12  $
//
// VERSION:     Visual dBASE
//
// DESCRIPTION:
//
// Main source file for dbfile, a Visual dBASE example.
//
// This example implements a series of textfile manipulation routines. These
// routines are accessible from Visual dBASE through the EXTERN system.
// A Textfile object on the Visual dBASE side initiates the contact by creating
// a corresponding C++ Textfile object through a call to TFinit which
// returns a pointer to that C++ object to the Visual dBASE object.  After that
// the Visual dBASE object can call into this DLL for specific routines.  The
// C++ routine calls back to the Visual dBASE object through DBase->GetThis()
// to get the this pointer of the corresponding C++ object and calls
// the wanted memberfunction of that object.
//
// Right now the available routines are:
//     close               - closes the current file if any.
//     eof                 - helps detect EOF.
//     error               - tells if an error was encountered.
//     fieldseparator      - sets the field separator.
//     filter              - sets the filter string.
//     geterror            - returns the last error encountered if any.
//     getfield            - gets the next field of the current record.
//     getline             - gets the next line.
//     getrec              - gets the next record.
//     lineseparator       - sets the line separator.
//     open                - opens a file.
//     release             - releases the C++ object.
//
// A line is ended by a lineseparator or EOF. If you add a '+' char
// to the char you send to "SetLineSeparator" it is assumed that
// the lineseparator is one or more char's of that lineseparator.
// A rec is a line that contains fields. Getrec sets up the fields
// and returns the number of fields in the current rec. A field is
// surrounded by one or more field separators including as special
// cases the start and the end of a line.
// If the filter is set a getline or getrec will search till
// a line is found that contains the filter string as a substring.
//
// The file dbfile.h will give a quick idea of how the C++ class is
// set up. The file dbfile.prg will tell you how things work on the
// Visual dBASE side.  The file dbasevar.h deals with the basic classes
// that ease the communication between C++ and Visual dBASE.
//
//****************************************************************************

   // Get the header.
#include "dbfile.h"

   // If you want to test the TextFile C++ class under DOS, define DOSTEST.
   // This will isolate the class from any Windows and Visual dBASE details and
   // allows this file to be compiled and linked with DOS programs.
#ifndef DOSTEST

//============================================================================
//
//  You can create an instance of class CSession in DBaseInitInstance()
//  and set it up. In subsequent call backs, a pointer to this instance
//  can be retrieved with a call to GetSession().  Data that is kept on
//  a per-instance basis (DBaseVars for example) should be kept in the
//  session object.
//
//============================================================================

class CSession {
public:

   // Local objects.

   CSession(){
       // Initialize objects local to this session.
   }
   ~CSession(){
       // Destroy objects local to this session.
   }

   // Memberfunctions to use local objects.
};


extern "C" {

//============================================================================
//
//  Usual DLL functions.
//
//============================================================================

int FAR PASCAL LibMain(HINSTANCE, WORD, WORD, LPSTR){

   return 1;
}
int CALLBACK WEP(int /*nParam*/){return(1);}

//
//  This function is called every time an Instance of Visual dBASE loads the DLL.
//  If for some reason the DLL determines that it cannot load, it can return
//  an error.  If the DLL loads properly it should return DBASE_INIT_OK.
//

int CALLBACK DBaseInitInstance(void){

   asm int 3;     // break point for the debugger.

   DBase()->SetSession(new CSession());

       //
       // Possible Error return Values
       //
       //return DBASE_INIT_NO_MULTI_INSTANCE;
       //return DBASE_INIT_ERROR;

   return DBASE_INIT_OK;
}

//
//  DBaseExitInstance() is called when an instance of Visual dBASE terminates or
//  manually unloads a DLL through the RELEASE DLL command.  This give the
//  DLL a chance to free any resources associated with a running instance.
//

void CALLBACK DBaseExitInstance(void){
}

   // End of DOSTEST define
#endif

//============================================================================
//
// Implementation of memberfunctions of the TextFile class.
//
//============================================================================

   // Some common defines.
#define LINE  0
#define REC   1

//
// AddToText( char*, char * ).
//
// Helper function to append a char string to the Text string. The
// Text buffer grows as needed to fit the largest rec or line
// encountered in the file.
//
// Returns 1 on success, 0 on error.
//

BOOL TextFile::AddToText( char * Begin, char * End ) {

   unsigned int Len;
   char SaveIt = *End;

       // Zero terminate the incoming string.
   *End = 0x0;
   Len = strlen( Begin );

       // See if we need more room.
   if( (TextLen + Len) > MaxTextLen ) {
       char * NewText;

#ifdef NO_EXCEPTIONS

       MaxTextLen = TextLen + Len;
       NewText = new char[ MaxTextLen + 1 ];
       if( NewText != NULL ) {
           strcpy( NewText, Text );
           delete [] Text;
           Text = NewText;
       }
       else {
               // Must be something wrong.
           Text[0] = 0x0;
           TextLen = 0;
           *End = SaveIt;
           return 0;
       }

#else  // NO_EXCEPTIONS

       try {
           MaxTextLen = TextLen + Len;
           NewText = new char[ MaxTextLen + 1 ];
           strcpy( NewText, Text );
           delete [] Text;
           Text = NewText;
       }
       catch(...) {

               // Must be something wrong.
           Text[0] = 0x0;
           TextLen = 0;
           *End = SaveIt;
           return 0;
       }

#endif  // NO_EXCEPTIONS

   }
       // Now simply copy it in.
   strcpy( Text + TextLen, Begin );
   TextLen += Len;

       // Restore.
   *End = SaveIt;

       // Return success.
   return 1;
}

//
// Close().
//
// Closes the file and sets Handle to 0.
//
// Returns 1 on success, 0 on error.
//

BOOL TextFile::Close() {

   if( Handle != 0 )
       if( close( Handle ) != 0 )
           return 0;

   Handle = 0;

       // return success.
   return 1;
}

//
// GetField( int ).
//
// This returns a pointer to the field specified by the WhichField
// parameter.  The fields are contained inside a rec separated by
// single fieldseparators.  When you send a field away, you simply insert
// a zero at the end of that field.  Next time you come back here you
// put that separator back which you can always find as the first char
// of the Text buffer.
//
// Returns pointer to field, or pointer to empty string in case of error.
//

char * TextFile::GetField( int WhichField ) {

   char *FieldStart = Text + 1, *FieldEnd = Text + 1, Separator = Text[0];

       // First see if we have to reinsert the end of a previous
       // field we sent away.
   if( SaveFieldEndPtr != NULL ) {
       *SaveFieldEndPtr = Text[0];
       SaveFieldEndPtr = NULL;
   }

       // Check if we have the field at all.
   if( WhichField <= 0 || WhichField > NumberOfFields )
       return "";

       // Find the field in the Text buffer.
   while( --WhichField > 0 ) {
       FieldStart = strchr( FieldStart, Separator );
       if( FieldStart == NULL ) {
           strcpy( ErrorString, "Record corrupted" );
           return "";
       }
       FieldStart++;
   }

       // Find the end and mark it.
   FieldEnd = strchr( FieldStart, Separator );
   if( FieldEnd != NULL ) {
       SaveFieldEndPtr = FieldEnd;
       *FieldEnd = 0x0;
   }

       // Send it away.
   return FieldStart;
}


//
// Open( char* ).
//
// If a file was already open, that file is closed before the
// requested file is opened.  Handle is set to the file.
//
// Returns 1 on success, 0 on error.
//

BOOL TextFile::Open( char *FileToOpen ) {

      // Is there already a file open.
      // If so, close it. Check for proper return.
   if( Handle != 0 )
      if( close( Handle ) != 0 )
         return 0;

      // Now open the file requested. Check for valid pointer return.
   if( ( Handle = open( FileToOpen, O_RDONLY | O_TEXT ) ) == -1 )
      return 0;

       // Start a new errorstring. Set up the Buffer.
   ErrorString[ 0 ] = 0x0;
   BufPtr = Buffer;
   Buffer[ 0 ] = 0x0;

      // Return success.
   return 1;
}

//
// ReadNextItem( char ).
//
// Reads the next item, either a line or a rec as indicated by the
// LineOrRec parameter, into the Text character buffer.  The file
// is buffer by buffer read into Buffer.  Buffer is scanned for
// separators and EOF.  After a field separator is found we handle
// field details if we're reading a rec.  After we find a line separator
// the BufPtr is set to after the separator for the next readitem.
// The Buffer is flushed into the Text buffer after each field and
// line or when the Buffer runs out.  At the end a complete next
// line or rec will be in the Text buffer.
//
// Returns Text on success, "" on error.
//

char * TextFile::ReadNextItem( char LineOrRec ) {

   char Done, Found = 0, StartOfRec;
   unsigned int NumberOfBytes;
   char BothSeparators[3] = { LineSeparator, 0x0, 0x0 };
   char * SepPtr = NULL, SepFound = 0x0;

       // In error state?
   if( Error() )
       return "";

       // Are we in business already?
   if( Handle == 0 ) {
       strcpy( ErrorString, "No file open\n" );
       return "";
   }

       // If we're scanning for a rec, we need the fieldseparator.
   if( LineOrRec == REC )
       BothSeparators[1] = FieldSeparator;

       // Keep reading till we found the next item.
   while( !Found ) {

           // Set up for the search and initialize Text.
       Done = 0;
       Text[0] = 0x0;
       TextLen = 0;
       NumberOfFields = 0;
       SaveFieldEndPtr = NULL;
       StartOfRec = (LineOrRec == REC );

           // Read and scan till we find a separator or EOF.
       while( !Done ) {

               // Did we run out of chars in the Buffer.
           if( *BufPtr == 0x0 ) {

                   // Read in a buffer full.
               NumberOfBytes = (unsigned int) read( Handle,
                                                    Buffer, BUFFERLENGTH );

                   // Check for EOF or error.
               if( NumberOfBytes == (unsigned int) -1 ||
                   NumberOfBytes == 0 ) {
                   if( NumberOfBytes != 0 ) {
                       strcpy( ErrorString, "Error Reading\n" );
                       return "";   // Error.
                   }
                   Done = 1;        // EOF.

                       // If there's nothing in the textbuffer we
                       // can't find a next item.
                   if( Text[0] == 0x0 ) {
                       Found = 1;
                   }
                       // But if there is something check if we have
                       // the last field of the file that we haven't
                       // counted yet.
                   else if( LineOrRec == REC && SepFound == 0x0 )
                       NumberOfFields++;

                   continue;
               }

                   // Set up for the search.
               BufPtr = Buffer;
               *( BufPtr + NumberOfBytes ) = 0x0;
               CopyPtr = Buffer;
           }

               // If we scan for a REC, first eat any fieldseparators
               // at the beginning. If we run out of Buffer, fall
               // through and read more. We also put the current
               // fieldseparator as the first char of the Text.
           if( StartOfRec == 1 ) {

               while( *BufPtr == FieldSeparator )
                   BufPtr++;
               if( *BufPtr == 0x0 )
                   continue;

               StartOfRec = 0;
               Text[ 0 ] = FieldSeparator;
               TextLen++;
               CopyPtr = BufPtr;
           }

               // Look for the next separator IF we're not already
               // inside a separator.
           if( SepFound == 0x0 ) {

                   // Cruise till we find a separator.
               SepPtr = strpbrk( BufPtr, BothSeparators );

                   // If we found a separator.
               if( SepPtr != NULL ) {

                       // Mark the end.
                   BufPtr = SepPtr;
                   SepFound = *SepPtr;

                       // If we are looking for fields.
                   if( LineOrRec == REC ) {

                           // Increase number of fields. Send one
                           // fieldseparator to text as well IF
                           // we found a fieldseparator.
                       NumberOfFields++;
                       if( SepFound == FieldSeparator )
                           BufPtr++;
                   }
               }
               else {  // We did not find a separator. Adjust BufPtr.

                   BufPtr += strlen( BufPtr );
               }

                   // Add what we have to Text.
               if( AddToText( CopyPtr, BufPtr ) == 0 ) {

                       // Error occured.
                   strcpy( ErrorString, "Line/Field too long" );
                   return "";
               }
           }

               // If we found a separator ( or are inside a separator ).
           if( SepFound != 0x0 ) {

                   // If we found a lineseparator and only singles of
                   // those are allowed, simply skip one and continue.
               if( SepFound == LineSeparator && PlusLineSeparator == 0 ) {
                   BufPtr++;
                   SepFound = 0x0;
                   Done = 1;
                   CopyPtr = BufPtr;
                   continue;
               }

                   // Now we scan till we run out of separators.
               while( *BufPtr == SepFound )
                   BufPtr++;

                   // If we found the end of the buffer, fall
                   // through and continue reading.
               if( *BufPtr == 0x0 )
                   continue;

                   // Special case for when we found a lineseparator
                   // after a bunch of fieldseparators. Means we have
                   // to fall through and scan for the end of the rec.
               if( *BufPtr == LineSeparator ) {
                   SepFound = LineSeparator;
                   continue;
               }

                   // We're done IF we were looking for lineseparator(s).
               if( SepFound == LineSeparator )
                   Done = 1;

                   // Either way, we're done with the separator found.
                   // And we can adjust copyptr.
               SepFound = 0x0;
               CopyPtr = BufPtr;
           }
       }

           // Now see if a filter is in place.
       if( Filter[0] != 0x0 ) {
           if( strstr( Text, Filter ) != NULL )
               Found = 1;
       }
       else {
               // Otherwise we have found what we wanted.
           Found = 1;
       }
   }

       // Return success.
   return Text;
}

//
// SetFilter( char* ).
//
// Sets the Filter char string. ReadNextItem only returns the next
// item IF it contains the Filter string as a substring.
// Can be called with SetFilter( "" ) to set the filter to nothing.
//
// Returns 1 on success. 0 on error.
//

BOOL TextFile::SetFilter( char *InFilter ) {

   unsigned int Len = strlen( InFilter );

       // If there's enough room, copy it in and leave.
   if( Len <= FilterLen ) {
       strcpy( Filter, InFilter );
       return 1;
   }
       // Not enough room, start with a clean slate.
   delete [] Filter;

       // Create some room.

#ifdef NO_EXCEPTIONS

   Filter = new char [ Len + 1 ];
   if( Filter != NULL ){
       FilterLen = Len;
   }
   else {
           // Guess not.
       strcpy( ErrorString, "Filter too long" );
       FilterLen = 0;
       return 0;
   }

#else // NO_EXCEPTIONS

   try {
       Filter = new char [ Len + 1 ];
       FilterLen = Len;
   }
   catch(...) {
           // Guess not.
       strcpy( ErrorString, "Filter too long" );
       FilterLen = 0;
       return 0;
   }

#endif // NO_EXCEPTIONS

   strcpy( Filter, InFilter );

       // Success.
   return 1;
}

//
// SetFieldSeparator( char * ).
//
// Sets the fieldseparator char.
//
// Returns 1 on success, 0 on error.
//

BOOL TextFile::SetFieldSeparator( char *Separator ) {

       // If nothing there, ignore, but return error.
   if( Separator == NULL )
       return 0;

       // Set the FieldSeparator.
   FieldSeparator = *Separator;

       // We did it.
   return 1;
}

//
// SetLineSeparator( char * ).
//
// Sets the lineseparator char. If the second char of the incoming
// string is a '+' it is assumed that the separator is multiples of
// the lineseparator char.
//
// ASSUMES a string is sent over, not a single character!!
//
// Returns 1 on success, 0 on error.
//

BOOL TextFile::SetLineSeparator( char *Separator ) {

       // If nothing there, ignore, but return error.
   if( Separator == NULL )
       return 0;

       // Set the LineSeparator and PlusLineSeparator if needed.
   LineSeparator = *Separator;
   if( *(Separator+1) == '+' )
       PlusLineSeparator = 1;
   else
       PlusLineSeparator = 0;

       // We did it.
   return 1;
}

   // If you want to test the C++ class under DOS, define DOSTEST.
#ifndef DOSTEST

//============================================================================

//
// TextFile routines called from the Visual dBASE side.
//

//============================================================================

//
// GetCPlusPlusThis().
//
// Helper function to get the C++ this pointer out of the Visual dBASE object.
//
// Returns a TextFile pointer.
//

TextFile* GetCPlusPlusThis() {

       // Local vars. Get the this of the Visual dBASE object.
   DVar CThis, DBaseThis( DBase()->GetThis() );

       // Get the this of the corresponding C++ object.
   DBaseThis->Property( "MYSTRUCT", CThis );

   return (TextFile*)(CThis->Long());
}

//
// TFclose().
//
// Closes the file.
//
// Returns 1 on success, 0 on error.
//

BOOL _export _pascal TFclose() {

   return GetCPlusPlusThis()->Close();
}

//
// TFeof().
//
// Returns 1 on EOF, 0 otherwise.
//

BOOL _export _pascal TFeof() {

   return GetCPlusPlusThis()->Eof();
}

//
// TFerror().
//
// Returns 1 on error set, 0 otherwise.
//

BOOL _export _pascal TFerror() {

   return GetCPlusPlusThis()->Error();
}

//
// TFfilter( char* ).
//
// Sets the filter.
//
// Returns 1 on success, 0 on error.
//

BOOL _export _pascal TFfilter( char * Filter ) {

   return GetCPlusPlusThis()->SetFilter( Filter );
}

//
// TFfieldseparator( char* ).
//
// Sets the fieldseparator char.
//
// Returns 1 on success, 0 on error.
//

BOOL _export _pascal TFfieldseparator( char *InString ) {

   return GetCPlusPlusThis()->SetFieldSeparator( InString );
}

//
// TFgeterror().
//
// Returns pointer to the errorstring of the object.
//

char * _export _pascal TFgeterror() {

   return GetCPlusPlusThis()->GetErrorString();
}

//
// TFgetfield( int ).
//
// Returna a pointer to the field as indicated by the parameter.
//
// Returns pointer to a field on success, "" on error.
//

char * _export _pascal TFgetfield( int WhichField ) {

   return GetCPlusPlusThis()->GetField( WhichField );
}

//
// TFgetrec()
//
// Returns # of fields in the next rec on success, 0 on error or EOF.
//

int _export _pascal TFgetrec() {

   TextFile * TFptr = GetCPlusPlusThis();
   if( TFptr->ReadNextItem( REC ) != NULL )
       return TFptr->GetNumberOfFields();
   else
       return 0;
}

//
// TFgetline().
//
// Returns pointer to the next line on success, "" on error or on EOF.
//

char * _export _pascal TFgetline() {

   return GetCPlusPlusThis()->ReadNextItem( LINE );
}

//
// TFinit().
//
// Initialization routine called from Visual dBASE when the Visual dBASE
// textfile object gets created.
//
// Return pointer (cast to a long) to a C++ TextFile object on success,
// 0 on error.
//

long _export _pascal TFinit() {

   TextFile * ThisTextFile;

#ifdef NO_EXCEPTIONS

       // Get the this of the CTextFile to be used.
   ThisTextFile = new TextFile;
   if( ThisTextFile != NULL ) {

           // Return pointer cast to a long.
       return (long) ThisTextFile;
   }
   else {

           // Failure.
       return 0L;
   }

#else  // NO_EXCEPTIONS

   try {

           // Get the this of the CTextFile to be used.
       ThisTextFile = new TextFile;

           // Return pointer cast to a long.
       return (long) ThisTextFile;
   }
   catch( ... ) {

           // Failure.
       return 0L;
   }

#endif  // NO_EXCEPTIONS

}

//
// TFlineseparator( char* ).
//
// Sets the line separator char.
//
// Returns 1 on success, 0 on error.
//

BOOL _export _pascal TFlineseparator( char *InString ) {

   return GetCPlusPlusThis()->SetLineSeparator( InString );
}

//
// TFopen( char* ).
//
// Opens a new file.
//
// Returns 1 on sucess, 0 on error.
//

BOOL _export _pascal TFopen( char * FileToOpen ) {

   return GetCPlusPlusThis()->Open( FileToOpen );
}

//
// TFrelease().
//
// Releases the C++ TextFile object.
//
// Returns 1 on sucess, 0 on error.
//

void _export _pascal TFrelease() {

   delete GetCPlusPlusThis();
}

}  // extern "C"

   // End of DOSTEST define
#endif
